88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/Koans/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 __, /pattern/.class
7 | end
8 |
9 | def test_a_regexp_can_search_a_string_for_matching_content
10 | assert_equal __, "some matching content"[/match/]
11 | end
12 |
13 | def test_a_failed_match_returns_nil
14 | assert_equal __, "some matching content"[/missing/]
15 | end
16 |
17 | # ------------------------------------------------------------------
18 |
19 | def test_question_mark_means_optional
20 | assert_equal __, "abbcccddddeeeee"[/ab?/]
21 | assert_equal __, "abbcccddddeeeee"[/az?/]
22 | end
23 |
24 | def test_plus_means_one_or_more
25 | assert_equal __, "abbcccddddeeeee"[/bc+/]
26 | end
27 |
28 | def test_asterisk_means_zero_or_more
29 | assert_equal __, "abbcccddddeeeee"[/ab*/]
30 | assert_equal __, "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 __, "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 __, 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 __, "the number is 42"[/[0123456789]+/]
59 | assert_equal __, "the number is 42"[/\d+/]
60 | end
61 |
62 | def test_character_classes_can_include_ranges
63 | assert_equal __, "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 __, "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 = 42"[/[a-zA-Z0-9_]+/]
73 | assert_equal __, "variable_1 = 42"[/\w+/]
74 | end
75 |
76 | def test_period_is_a_shortcut_for_any_non_newline_character
77 | assert_equal __, "abc\n123"[/a.+/]
78 | end
79 |
80 | def test_a_character_class_can_be_negated
81 | assert_equal __, "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 42"[/\D+/]
86 | assert_equal __, "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 end"[/\Astart/]
96 | assert_equal __, "start end"[/\Aend/]
97 | end
98 |
99 | def test_slash_z_anchors_to_the_end_of_the_string
100 | assert_equal __, "start end"[/end\z/]
101 | assert_equal __, "start end"[/start\z/]
102 | end
103 |
104 | def test_caret_anchors_to_the_start_of_lines
105 | assert_equal __, "num 42\n2 lines"[/^\d+/]
106 | end
107 |
108 | def test_dollar_sign_anchors_to_the_end_of_lines
109 | assert_equal __, "2 lines\nnum 42"[/\d+$/]
110 | end
111 |
112 | def test_slash_b_anchors_to_a_word_boundary
113 | assert_equal __, "bovine vines"[/\bvine./]
114 | end
115 |
116 | # ------------------------------------------------------------------
117 |
118 | def test_parentheses_group_contents
119 | assert_equal __, "ahahaha"[/(ha)+/]
120 | end
121 |
122 | # ------------------------------------------------------------------
123 |
124 | def test_parentheses_also_capture_matched_content_by_number
125 | assert_equal __, "Gray, James"[/(\w+), (\w+)/, 1]
126 | assert_equal __, "Gray, James"[/(\w+), (\w+)/, 2]
127 | end
128 |
129 | def test_variables_can_also_be_used_to_access_captures
130 | assert_equal __, "Name: Gray, James"[/(\w+), (\w+)/]
131 | assert_equal __, $1
132 | assert_equal __, $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"[grays]
140 | assert_equal __, "Summer Gray"[grays, 1]
141 | assert_equal __, "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".scan(/\w+/)
152 | end
153 |
154 | def test_sub_is_like_find_and_replace
155 | assert_equal __, "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 two-three".gsub(/(t\w*)/) { $1[0, 1] }
160 | end
161 | end
162 |
--------------------------------------------------------------------------------
/Koans/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?".____ ) # 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 __, 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 __, mc.respond_to?(:caught?)
45 | assert_equal __, 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 __, mc.add_a_payload(3, 4, nil, 6)
63 | assert_equal __, 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(___) 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(___) 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 __, catcher.foobar
125 | assert_equal __, catcher.foobaz(1)
126 | assert_equal __, 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 __, 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 __, catcher.foo_bar
154 | assert_equal __, catcher.foo_baz
155 | end
156 |
157 | def test_non_foo_messages_are_treated_normally
158 | catcher = WellBehavedFooCatcher.new
159 |
160 | assert_raise(___) 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 __, catcher.respond_to?(:foo_bar)
182 | assert_equal __, catcher.respond_to?(:something_else)
183 | end
184 |
185 | end
186 |
--------------------------------------------------------------------------------
/Koans/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 __, string.is_a?(String)
7 | end
8 |
9 | def test_single_quoted_strings_are_also_strings
10 | string = 'Goodbye, World'
11 | assert_equal __, 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 __, string
17 | end
18 |
19 | def test_use_double_quotes_to_create_strings_with_single_quotes
20 | string = "Don't"
21 | assert_equal __, 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 __, 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 __, a == b
35 | assert_equal __, 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 __, long_string.length
44 | assert_equal __, long_string.lines.count
45 | assert_equal __, long_string[0,1]
46 | end
47 |
48 | def test_here_documents_can_also_handle_multiple_lines
49 | long_string = < **An absolute position must have:**
68 |
69 | >* a property position: absolute (fair enough...)
70 | * coordinates such as top/right/bottom/left, pixels or relatives lengths (ok..)
71 | * **an ancestor element with an explicitly declared position property with a value of relative or absolute - if no ancestor has a declared position property, the absolutley defined positioned element with be placed relevative to the element (Yikes!)**
72 |
73 | `Chapter 5: Adaptive Layouts with Media Queries`
74 | --
75 |
76 | This chapter was a breeze - and relatively short - but quality. I particularly appreciate the way the authors pepper in a little bit of detail to explain things, such as why media queries almost always use 'all':
77 |
78 | `@media all and ... {
79 | /*Styles go here*/
80 | }`
81 |
82 | When media queries were first being introduced it was planned that in the place of 'all' would eventually be device specific tags such as `phone` or `tv` ... or just as likely `toaster` or `refridgerator` these days it seems. These device specific tags haven't really worked out as planned so 'all' is the catch-all.
83 |
84 | I always find little bits of context or explanation like this help me remember things.
85 |
86 | `Chapter 6: Handling Events in Javascript`
87 | --
88 |
89 | "Bottom up approach". Another reminder to start small and work up. A reminder that Nodelist's existed and generally should always be transmuted into a regular JS Array.
90 |
91 | Generally a good practice/refresher chapter.
92 |
93 | `Chapter 7: Visual Effects with CSS`
94 | --
95 | After a short break from finishing the book due to work and commitments, what can I say, this chapter blew my socks of (with [cubic-bezier transitions](http://cubic-bezier.com/#.17,.67,.83,.67) you might even say).
96 |
97 | One 'aha' moment was being exposed to the keycode event objects that are easily loggable. This made a bunch of code I had seen before including early hard-coded games involving playing keyboards to music immediately make more sense.
98 |
99 | `Chapters 8, 9, 10`
100 | --
101 | TBC. Chapter 8 on Modules is one of the longer and more difficult and most valuable to revise. I am happy to say that the chapters on Bootstrap and Jquery/Form Handling that followed were relatively a breeze.
102 |
103 |
104 | ``Javascript30``
105 | --
106 | 1. WesBos starts outs with a fun challenge focused on using keyevents to interact with DOM elements to play music. Keywords:
107 | * ES6 Template Strings
108 | * classList.add(`css class goes here`)
109 | * Drawbacks of using set-timeouts with css transition effects
110 | * `data-*` data attribute practices
111 |
112 |
113 | ``Koans``
114 | --
115 | `0-15`
116 |
117 | * Every object has a unique object id's, small integers have fixed object id's... and these folow the fibonnaci sequence for some reason.
118 | * `assert_equal true, nil.nil?`is apparently the only method on nil that returns true
--------------------------------------------------------------------------------
/Koans/README.rdoc:
--------------------------------------------------------------------------------
1 | = Neo 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
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 +watchr+ 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 watchr
152 | watchr ./koans/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 :: http://www.pivotaltracker.com/projects/48111
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 |
--------------------------------------------------------------------------------
/Koans/neo.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # -*- ruby -*-
3 |
4 | begin
5 | require 'win32console'
6 | rescue LoadError
7 | end
8 |
9 | # --------------------------------------------------------------------
10 | # Support code for the Ruby Koans.
11 | # --------------------------------------------------------------------
12 |
13 | class FillMeInError < StandardError
14 | end
15 |
16 | def ruby_version?(version)
17 | RUBY_VERSION =~ /^#{version}/ ||
18 | (version == 'jruby' && defined?(JRUBY_VERSION)) ||
19 | (version == 'mri' && ! defined?(JRUBY_VERSION))
20 | end
21 |
22 | def in_ruby_version(*versions)
23 | yield if versions.any? { |v| ruby_version?(v) }
24 | end
25 |
26 | in_ruby_version("1.8") do
27 | class KeyError < StandardError
28 | end
29 | end
30 |
31 | # Standard, generic replacement value.
32 | # If value19 is given, it is used in place of value for Ruby 1.9.
33 | def __(value="FILL ME IN", value19=:mu)
34 | if RUBY_VERSION < "1.9"
35 | value
36 | else
37 | (value19 == :mu) ? value : value19
38 | end
39 | end
40 |
41 | # Numeric replacement value.
42 | def _n_(value=999999, value19=:mu)
43 | if RUBY_VERSION < "1.9"
44 | value
45 | else
46 | (value19 == :mu) ? value : value19
47 | end
48 | end
49 |
50 | # Error object replacement value.
51 | def ___(value=FillMeInError, value19=:mu)
52 | if RUBY_VERSION < "1.9"
53 | value
54 | else
55 | (value19 == :mu) ? value : value19
56 | end
57 | end
58 |
59 | # Method name replacement.
60 | class Object
61 | def ____(method=nil)
62 | if method
63 | self.send(method)
64 | end
65 | end
66 |
67 | in_ruby_version("1.9", "2") do
68 | public :method_missing
69 | end
70 | end
71 |
72 | class String
73 | def side_padding(width)
74 | extra = width - self.size
75 | if width < 0
76 | self
77 | else
78 | left_padding = extra / 2
79 | right_padding = (extra+1)/2
80 | (" " * left_padding) + self + (" " *right_padding)
81 | end
82 | end
83 | end
84 |
85 | module Neo
86 | class << self
87 | def simple_output
88 | ENV['SIMPLE_KOAN_OUTPUT'] == 'true'
89 | end
90 | end
91 |
92 | module Color
93 | #shamelessly stolen (and modified) from redgreen
94 | COLORS = {
95 | :clear => 0, :black => 30, :red => 31,
96 | :green => 32, :yellow => 33, :blue => 34,
97 | :magenta => 35, :cyan => 36,
98 | }
99 |
100 | module_function
101 |
102 | COLORS.each do |color, value|
103 | module_eval "def #{color}(string); colorize(string, #{value}); end"
104 | module_function color
105 | end
106 |
107 | def colorize(string, color_value)
108 | if use_colors?
109 | color(color_value) + string + color(COLORS[:clear])
110 | else
111 | string
112 | end
113 | end
114 |
115 | def color(color_value)
116 | "\e[#{color_value}m"
117 | end
118 |
119 | def use_colors?
120 | return false if ENV['NO_COLOR']
121 | if ENV['ANSI_COLOR'].nil?
122 | if using_windows?
123 | using_win32console
124 | else
125 | return true
126 | end
127 | else
128 | ENV['ANSI_COLOR'] =~ /^(t|y)/i
129 | end
130 | end
131 |
132 | def using_windows?
133 | File::ALT_SEPARATOR
134 | end
135 |
136 | def using_win32console
137 | defined? Win32::Console
138 | end
139 | end
140 |
141 | module Assertions
142 | FailedAssertionError = Class.new(StandardError)
143 |
144 | def flunk(msg)
145 | raise FailedAssertionError, msg
146 | end
147 |
148 | def assert(condition, msg=nil)
149 | msg ||= "Failed assertion."
150 | flunk(msg) unless condition
151 | true
152 | end
153 |
154 | def assert_equal(expected, actual, msg=nil)
155 | msg ||= "Expected #{expected.inspect} to equal #{actual.inspect}"
156 | assert(expected == actual, msg)
157 | end
158 |
159 | def assert_not_equal(expected, actual, msg=nil)
160 | msg ||= "Expected #{expected.inspect} to not equal #{actual.inspect}"
161 | assert(expected != actual, msg)
162 | end
163 |
164 | def assert_nil(actual, msg=nil)
165 | msg ||= "Expected #{actual.inspect} to be nil"
166 | assert(nil == actual, msg)
167 | end
168 |
169 | def assert_not_nil(actual, msg=nil)
170 | msg ||= "Expected #{actual.inspect} to not be nil"
171 | assert(nil != actual, msg)
172 | end
173 |
174 | def assert_match(pattern, actual, msg=nil)
175 | msg ||= "Expected #{actual.inspect} to match #{pattern.inspect}"
176 | assert pattern =~ actual, msg
177 | end
178 |
179 | def assert_raise(exception)
180 | begin
181 | yield
182 | rescue Exception => ex
183 | expected = ex.is_a?(exception)
184 | assert(expected, "Exception #{exception.inspect} expected, but #{ex.inspect} was raised")
185 | return ex
186 | end
187 | flunk "Exception #{exception.inspect} expected, but nothing raised"
188 | end
189 |
190 | def assert_nothing_raised
191 | begin
192 | yield
193 | rescue Exception => ex
194 | flunk "Expected nothing to be raised, but exception #{exception.inspect} was raised"
195 | end
196 | end
197 | end
198 |
199 | class Sensei
200 | attr_reader :failure, :failed_test, :pass_count
201 |
202 | FailedAssertionError = Assertions::FailedAssertionError
203 |
204 | def initialize
205 | @pass_count = 0
206 | @failure = nil
207 | @failed_test = nil
208 | @observations = []
209 | end
210 |
211 | PROGRESS_FILE_NAME = '.path_progress'
212 |
213 | def add_progress(prog)
214 | @_contents = nil
215 | exists = File.exists?(PROGRESS_FILE_NAME)
216 | File.open(PROGRESS_FILE_NAME,'a+') do |f|
217 | f.print "#{',' if exists}#{prog}"
218 | end
219 | end
220 |
221 | def progress
222 | if @_contents.nil?
223 | if File.exists?(PROGRESS_FILE_NAME)
224 | File.open(PROGRESS_FILE_NAME,'r') do |f|
225 | @_contents = f.read.to_s.gsub(/\s/,'').split(',')
226 | end
227 | else
228 | @_contents = []
229 | end
230 | end
231 | @_contents
232 | end
233 |
234 | def observe(step)
235 | if step.passed?
236 | @pass_count += 1
237 | if @pass_count > progress.last.to_i
238 | @observations << Color.green("#{step.koan_file}##{step.name} has expanded your awareness.")
239 | end
240 | else
241 | @failed_test = step
242 | @failure = step.failure
243 | add_progress(@pass_count)
244 | @observations << Color.red("#{step.koan_file}##{step.name} has damaged your karma.")
245 | throw :neo_exit
246 | end
247 | end
248 |
249 | def failed?
250 | ! @failure.nil?
251 | end
252 |
253 | def assert_failed?
254 | failure.is_a?(FailedAssertionError)
255 | end
256 |
257 | def instruct
258 | if failed?
259 | @observations.each{|c| puts c }
260 | encourage
261 | guide_through_error
262 | a_zenlike_statement
263 | show_progress
264 | else
265 | end_screen
266 | end
267 | end
268 |
269 | def show_progress
270 | bar_width = 50
271 | total_tests = Neo::Koan.total_tests
272 | scale = bar_width.to_f/total_tests
273 | print Color.green("your path thus far [")
274 | happy_steps = (pass_count*scale).to_i
275 | happy_steps = 1 if happy_steps == 0 && pass_count > 0
276 | print Color.green('.'*happy_steps)
277 | if failed?
278 | print Color.red('X')
279 | print Color.cyan('_'*(bar_width-1-happy_steps))
280 | end
281 | print Color.green(']')
282 | print " #{pass_count}/#{total_tests}"
283 | puts
284 | end
285 |
286 | def end_screen
287 | if Neo.simple_output
288 | boring_end_screen
289 | else
290 | artistic_end_screen
291 | end
292 | end
293 |
294 | def boring_end_screen
295 | puts "Mountains are again merely mountains"
296 | end
297 |
298 | def artistic_end_screen
299 | "JRuby 1.9.x Koans"
300 | ruby_version = "(in #{'J' if defined?(JRUBY_VERSION)}Ruby #{defined?(JRUBY_VERSION) ? JRUBY_VERSION : RUBY_VERSION})"
301 | ruby_version = ruby_version.side_padding(54)
302 | completed = <<-ENDTEXT
303 | ,, , ,,
304 | : ::::, :::,
305 | , ,,: :::::::::::::,, :::: : ,
306 | , ,,, ,:::::::::::::::::::, ,: ,: ,,
307 | :, ::, , , :, ,::::::::::::::::::, ::: ,::::
308 | : : ::, ,:::::::: ::, ,::::
309 | , ,::::: :,:::::::,::::,
310 | ,: , ,:,,: :::::::::::::
311 | ::,: ,,:::, ,::::::::::::,
312 | ,:::, :,,::: ::::::::::::,
313 | ,::: :::::::, Mountains are again merely mountains ,::::::::::::
314 | :::,,,:::::: ::::::::::::
315 | ,:::::::::::, ::::::::::::,
316 | :::::::::::, ,::::::::::::
317 | ::::::::::::: ,::::::::::::
318 | :::::::::::: Ruby Koans ::::::::::::
319 | ::::::::::::#{ ruby_version },::::::::::::
320 | :::::::::::, , :::::::::::
321 | ,:::::::::::::, brought to you by ,,::::::::::::
322 | :::::::::::::: ,::::::::::::
323 | ::::::::::::::, ,:::::::::::::
324 | ::::::::::::, Neo Software Artisans , ::::::::::::
325 | :,::::::::: :::: :::::::::::::
326 | ,::::::::::: ,: ,,:::::::::::::,
327 | :::::::::::: ,::::::::::::::,
328 | :::::::::::::::::, ::::::::::::::::
329 | :::::::::::::::::::, ::::::::::::::::
330 | ::::::::::::::::::::::, ,::::,:, , ::::,:::
331 | :::::::::::::::::::::::, ::,: ::,::, ,,: ::::
332 | ,:::::::::::::::::::: ::,, , ,, ,::::
333 | ,:::::::::::::::: ::,, , ,:::,
334 | ,:::: , ,,
335 | ,,,
336 | ENDTEXT
337 | puts completed
338 | end
339 |
340 | def encourage
341 | puts
342 | puts "The Master says:"
343 | puts Color.cyan(" You have not yet reached enlightenment.")
344 | if ((recents = progress.last(5)) && recents.size == 5 && recents.uniq.size == 1)
345 | puts Color.cyan(" I sense frustration. Do not be afraid to ask for help.")
346 | elsif progress.last(2).size == 2 && progress.last(2).uniq.size == 1
347 | puts Color.cyan(" Do not lose hope.")
348 | elsif progress.last.to_i > 0
349 | puts Color.cyan(" You are progressing. Excellent. #{progress.last} completed.")
350 | end
351 | end
352 |
353 | def guide_through_error
354 | puts
355 | puts "The answers you seek..."
356 | puts Color.red(indent(failure.message).join)
357 | puts
358 | puts "Please meditate on the following code:"
359 | puts embolden_first_line_only(indent(find_interesting_lines(failure.backtrace)))
360 | puts
361 | end
362 |
363 | def embolden_first_line_only(text)
364 | first_line = true
365 | text.collect { |t|
366 | if first_line
367 | first_line = false
368 | Color.red(t)
369 | else
370 | Color.cyan(t)
371 | end
372 | }
373 | end
374 |
375 | def indent(text)
376 | text = text.split(/\n/) if text.is_a?(String)
377 | text.collect{|t| " #{t}"}
378 | end
379 |
380 | def find_interesting_lines(backtrace)
381 | backtrace.reject { |line|
382 | line =~ /neo\.rb/
383 | }
384 | end
385 |
386 | # Hat's tip to Ara T. Howard for the zen statements from his
387 | # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html)
388 | def a_zenlike_statement
389 | if !failed?
390 | zen_statement = "Mountains are again merely mountains"
391 | else
392 | zen_statement = case (@pass_count % 10)
393 | when 0
394 | "mountains are merely mountains"
395 | when 1, 2
396 | "learn the rules so you know how to break them properly"
397 | when 3, 4
398 | "remember that silence is sometimes the best answer"
399 | when 5, 6
400 | "sleep is the best meditation"
401 | when 7, 8
402 | "when you lose, don't lose the lesson"
403 | else
404 | "things are not what they appear to be: nor are they otherwise"
405 | end
406 | end
407 | puts Color.green(zen_statement)
408 | end
409 | end
410 |
411 | class Koan
412 | include Assertions
413 |
414 | attr_reader :name, :failure, :koan_count, :step_count, :koan_file
415 |
416 | def initialize(name, koan_file=nil, koan_count=0, step_count=0)
417 | @name = name
418 | @failure = nil
419 | @koan_count = koan_count
420 | @step_count = step_count
421 | @koan_file = koan_file
422 | end
423 |
424 | def passed?
425 | @failure.nil?
426 | end
427 |
428 | def failed(failure)
429 | @failure = failure
430 | end
431 |
432 | def setup
433 | end
434 |
435 | def teardown
436 | end
437 |
438 | def meditate
439 | setup
440 | begin
441 | send(name)
442 | rescue StandardError, Neo::Sensei::FailedAssertionError => ex
443 | failed(ex)
444 | ensure
445 | begin
446 | teardown
447 | rescue StandardError, Neo::Sensei::FailedAssertionError => ex
448 | failed(ex) if passed?
449 | end
450 | end
451 | self
452 | end
453 |
454 | # Class methods for the Neo test suite.
455 | class << self
456 | def inherited(subclass)
457 | subclasses << subclass
458 | end
459 |
460 | def method_added(name)
461 | testmethods << name if !tests_disabled? && Koan.test_pattern =~ name.to_s
462 | end
463 |
464 | def end_of_enlightenment
465 | @tests_disabled = true
466 | end
467 |
468 | def command_line(args)
469 | args.each do |arg|
470 | case arg
471 | when /^-n\/(.*)\/$/
472 | @test_pattern = Regexp.new($1)
473 | when /^-n(.*)$/
474 | @test_pattern = Regexp.new(Regexp.quote($1))
475 | else
476 | if File.exist?(arg)
477 | load(arg)
478 | else
479 | fail "Unknown command line argument '#{arg}'"
480 | end
481 | end
482 | end
483 | end
484 |
485 | # Lazy initialize list of subclasses
486 | def subclasses
487 | @subclasses ||= []
488 | end
489 |
490 | # Lazy initialize list of test methods.
491 | def testmethods
492 | @test_methods ||= []
493 | end
494 |
495 | def tests_disabled?
496 | @tests_disabled ||= false
497 | end
498 |
499 | def test_pattern
500 | @test_pattern ||= /^test_/
501 | end
502 |
503 | def total_tests
504 | self.subclasses.inject(0){|total, k| total + k.testmethods.size }
505 | end
506 | end
507 | end
508 |
509 | class ThePath
510 | def walk
511 | sensei = Neo::Sensei.new
512 | each_step do |step|
513 | sensei.observe(step.meditate)
514 | end
515 | sensei.instruct
516 | end
517 |
518 | def each_step
519 | catch(:neo_exit) {
520 | step_count = 0
521 | Neo::Koan.subclasses.each_with_index do |koan,koan_index|
522 | koan.testmethods.each do |method_name|
523 | step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
524 | yield step
525 | end
526 | end
527 | }
528 | end
529 | end
530 | end
531 |
532 | END {
533 | Neo::Koan.command_line(ARGV)
534 | Neo::ThePath.new.walk
535 | }
536 |
--------------------------------------------------------------------------------
/ottergram/stylesheets/fonts/LAKESHOR-webfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------