tryruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2b4cc48e47a9d65df21ad93130190174011d3fdd
4
+ data.tar.gz: df7553c3ae34839e4102efd03c2af1acce62d92a
5
+ SHA512:
6
+ metadata.gz: d5dbef89b29e262fe4afa77e16e44e43c84a2ad5725fba3bfdc1459ed1d803bfcf060a338e5dc2d851b2a0d9c40315bda9dc8d7db68b9395579c38482a481e08
7
+ data.tar.gz: 450ee857525876adfebf6fa40fcc5733194cf48a4ae1e372b8c27a1d54673bcf9e2f2312808a6281decb49602a415f1d60731073ce5741b8828c73ace5b12e6b
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
@@ -0,0 +1,2 @@
1
+ Lint/Eval:
2
+ Enabled: false
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ sudo: false
3
+ rvm:
4
+ - 2.0.0
5
+ - 2.1.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in try_ruby_cli.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Jakub Červenka
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ # Try Ruby!
2
+
3
+ [![Build Status](https://travis-ci.org/cvut/tryruby.svg?branch=master)](https://travis-ci.org/cvut/tryruby)
4
+
5
+ A command line version of [tryruby.org](http://tryruby.org/) in case that the website goes down or is having troubles with too many simultaneous connections you can use this REPL shell to try ruby!
6
+
7
+ ## Installation
8
+
9
+ $ gem install tryruby
10
+
11
+ ## Usage
12
+
13
+ $ tryruby
14
+
15
+ ## Contributing
16
+
17
+ 1. Fork it ( https://github.com/cvut/tryruby/fork )
18
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
19
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
20
+ 4. Push to the branch (`git push origin my-new-feature`)
21
+ 5. Create a new Pull Request
22
+
23
+ ## TODO
24
+
25
+ * convert Why's drawings to ASCII art
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+ require_relative File.expand_path('../lib/tryruby/runner', File.dirname(__FILE__))
4
+
5
+ ARGV << 'Tryruby::DefaultLevels' unless ARGV.length > 0
6
+ tutorials = ARGV.map do |tutorial|
7
+ snake_case = tutorial.gsub(/::/, '/')
8
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
9
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
10
+ .tr('-', '_')
11
+ .downcase
12
+ begin
13
+ require snake_case
14
+ rescue LoadError
15
+ require_relative File.expand_path("../lib/#{snake_case}", File.dirname(__FILE__))
16
+ end
17
+ Object.const_get(tutorial).new
18
+ end
19
+ ARGV.clear
20
+
21
+ Tryruby::Runner.start(tutorials)
@@ -0,0 +1,5 @@
1
+ require 'tryruby/version'
2
+
3
+ # CLI version of tryruby.org
4
+ module Tryruby
5
+ end
@@ -0,0 +1,28 @@
1
+ require 'sourcify'
2
+
3
+ module Tryruby
4
+ # Single tutorial challenge
5
+ class Challenge
6
+ attr_reader :help, :display_setup
7
+
8
+ def initialize(help, test = nil, setup = nil, display_setup = false)
9
+ @help, @test, @setup, @display_setup = help, test, setup, display_setup
10
+ end
11
+
12
+ def test(repl, result, vars, output)
13
+ repl.instance_exec(result, vars, output, &@test) if @test
14
+ end
15
+
16
+ def setup(repl, vars)
17
+ repl.instance_exec(vars, &@setup) if @setup
18
+ end
19
+
20
+ # Challenge setup as text
21
+ def setup_source
22
+ return '' unless @setup
23
+ vars_name = @setup.parameters[0][1]
24
+ source = @setup.to_source(strip_enclosure: true)
25
+ source.gsub(/#{vars_name}\[:([^\]]+)\]/, '\1')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'challenge'
2
+ require_relative 'colors'
3
+
4
+ module Tryruby
5
+ # Single challenge level builder
6
+ class ChallengeBuilder
7
+ include Colors
8
+
9
+ attr_writer :help, :test, :setup, :display_setup
10
+ alias_method :help, :help=
11
+ alias_method :display_setup, :display_setup=
12
+
13
+ def initialize
14
+ @help = ''
15
+ @test = nil
16
+ @setup = nil
17
+ @display_setup = false
18
+ end
19
+
20
+ def test(&block)
21
+ self.test = block
22
+ end
23
+
24
+ def setup(&block)
25
+ self.setup = block
26
+ end
27
+
28
+ def challenge
29
+ Challenge.new(@help, @test, @setup, @display_setup)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,43 @@
1
+ require 'paint'
2
+
3
+ module Tryruby
4
+ # ANSI formatting shortcuts
5
+ module Colors
6
+ Paint::SHORTCUTS[:tryruby] = {
7
+ black: Paint.color(:black),
8
+ red: Paint.color(:red),
9
+ green: Paint.color(:green),
10
+ yellow: Paint.color(:yellow),
11
+ blue: Paint.color(:blue),
12
+ magenta: Paint.color(:magenta),
13
+ cyan: Paint.color(:cyan),
14
+ white: Paint.color(:white),
15
+ bg_black: Paint.color(nil, :black),
16
+ bg_red: Paint.color(nil, :red),
17
+ bg_green: Paint.color(nil, :green),
18
+ bg_yellow: Paint.color(nil, :yellow),
19
+ bg_blue: Paint.color(nil, :blue),
20
+ bg_magenta: Paint.color(nil, :magenta),
21
+ bg_cyan: Paint.color(nil, :cyan),
22
+ bg_white: Paint.color(nil, :white),
23
+ reset: Paint.color(:reset),
24
+ bold: Paint.color(:bold),
25
+ underline: Paint.color(:underline),
26
+ inverse: Paint.color(:inverse),
27
+ hide: Paint.color(:hide),
28
+ title: Paint.color(:bold, :underline),
29
+ result: Paint.color(:blue),
30
+ error: Paint.color(:red, :bold),
31
+ success: Paint.color(:green, :bold)
32
+ }
33
+
34
+ include Paint::Tryruby
35
+
36
+ def paint(*args)
37
+ Paint[*args]
38
+ end
39
+
40
+ module_function(*Paint::SHORTCUTS[:tryruby].keys)
41
+ module_function :paint
42
+ end
43
+ end
@@ -0,0 +1,18 @@
1
+ require 'ripl'
2
+
3
+ module Tryruby
4
+ # Basic commands
5
+ module Commands
6
+ def next
7
+ Ripl.shell.next_challenge
8
+ end
9
+
10
+ def back
11
+ Ripl.shell.prev_challenge
12
+ end
13
+
14
+ def help
15
+ Ripl.shell.help_challenge
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,858 @@
1
+ require_relative 'tutorial'
2
+ require 'fileutils'
3
+
4
+ module Tryruby
5
+ # Levels and challenges form tryruby.org
6
+ class DefaultLevels < Tutorial
7
+ level do
8
+ challenge do
9
+ help <<-EOF
10
+ #{title 'Got 15 minutes? Give Ruby a shot right now!'}
11
+
12
+ Ruby is a programming language from Japan (available at ruby-lang.org) which is
13
+ revolutionizing the web. The beauty of Ruby is found in its balance between
14
+ simplicity and power.
15
+
16
+ Try out Ruby code in the prompt. In addition to Ruby's built-in methods, the
17
+ following commands are available:
18
+
19
+ * #{inverse 'help'} → Repeat last instructions.
20
+ * #{inverse 'next'} → Allows you to skip to the next section of a lesson.
21
+ * #{inverse 'back'} → Allows you to return to the previous section of a \
22
+ lesson.
23
+ EOF
24
+ end
25
+
26
+ challenge do
27
+ help <<-EOF
28
+ #{title 'Using the Prompt'}
29
+
30
+ The prompt below is a Ruby prompt.
31
+
32
+ Here you'll be able to type a line of Ruby code, hit #{inverse 'Enter'}, and \
33
+ watch it run!
34
+
35
+ For your first bit of Ruby, try typing some math, like: #{result '2 + 6'}
36
+ EOF
37
+ test { |result| result == 8 }
38
+ end
39
+
40
+ challenge do
41
+ help <<-EOF
42
+ Great! You did a little bit of math.
43
+
44
+ See how the answer was returned? Ruby uses a fat arrow for responses to your
45
+ entries.
46
+
47
+ Ruby recognizes numbers and mathematics operators. You could also try some other
48
+ math like:
49
+
50
+ * #{result '4 * 10'}
51
+ * #{result '5 - 12'}
52
+ * #{result '40 / 4'}
53
+
54
+ Even though we've placed a space between the numbers and the operators above,
55
+ it's not required. For now, stick with these basic operations; we'll try a few
56
+ others later.
57
+
58
+ When you're finished experimenting, type #{result 'next'} to move to the next \
59
+ lesson when
60
+ you're finished.
61
+ EOF
62
+ end
63
+
64
+ challenge do
65
+ help <<-EOF
66
+ #{title 'Say Your Name'}
67
+
68
+ Welp, we already know that computers are handy and fast for math.
69
+
70
+ But what about something really useful. Like, say, seeing the letters of your
71
+ name reversed!
72
+
73
+ To do that super-cool task, let's first get you familiar with text in Ruby. Type
74
+ your first name in quotes, like this: #{result '"Jimmy"'}
75
+ EOF
76
+ test { |res| res.is_a?(String) && res.length > 0 }
77
+ end
78
+
79
+ challenge do
80
+ help <<-EOF
81
+ #{title 'Say Your Name Reversed'}
82
+
83
+ Perfect, you've formed a #{inverse 'string'} from the letters of your name. \
84
+ A string is an
85
+ ordered set of characters that the computer can process.
86
+
87
+ Imagine the letters are on a string of laundry line and the quotes are
88
+ clothespins holding the ends. The quotes mark the beginning and the end of the
89
+ string, and are required.
90
+
91
+ Now, let's get to reversing your name.
92
+
93
+ Type: #{result '"Jimmy".reverse'}, using your own name where the string goes. \
94
+ (Don't forget
95
+ the dot!)
96
+ EOF
97
+ test { |res| res.is_a?(String) && res.length > 0 }
98
+ end
99
+
100
+ challenge do
101
+ help <<-EOF
102
+ #{title 'Counting the Letters'}
103
+
104
+ You have used the #{inverse 'reverse'} method on your name!
105
+
106
+ By enclosing your name in quotes, you made a string. Next, you used a dot to
107
+ access a hidden list of methods that belong to all strings. In this case, you
108
+ then called the #{inverse 'reverse'} method, which works on strings to flip \
109
+ the order of the
110
+ string’s characters. Cool, huh?
111
+
112
+ Now, let's use a different option which will show us how many letters are in
113
+ your name. Try typing #{result '"Jimmy".length'} using your name where the \
114
+ string goes.
115
+ EOF
116
+ test { |res| res.is_a?(Fixnum) && res > 0 }
117
+ end
118
+
119
+ challenge do
120
+ help <<-EOF
121
+ #{title 'On Repeat'}
122
+
123
+ Now, maybe you're wondering what any of this is actually good for. Have you ever
124
+ encountered a website that yelled at you for choosing a password that was too
125
+ short? Turns out, the #{inverse 'length'} property is often what that site \
126
+ uses to check for
127
+ a correct length.
128
+
129
+ Let's get crazy now, and multiply your name by 5. Follow the following format:
130
+ #{result '"Jimmy" * 5'}
131
+ EOF
132
+ test { |res| res.is_a?(String) && res.length > 0 }
133
+ end
134
+ end
135
+
136
+ level do
137
+ challenge do
138
+ help <<-EOF
139
+ #{title 'Hey, Whoa, Level #2 Already'}
140
+
141
+ Let's look at what you've learned in the first minute.
142
+
143
+ * #{bold 'The prompt'}. Typing code into the prompt gives you an answer.
144
+ * #{bold 'Numbers and strings'} are Ruby's math and text objects.
145
+ * #{bold 'Methods'}. You've used English-language methods like \
146
+ #{inverse 'reverse'} and symbolic
147
+ methods like #{inverse '*'} (the multiplication method.) Methods are \
148
+ actions!
149
+
150
+ This is the essence of your learning. Taking simple things, toying with them and
151
+ turning them into new things! Feeling comfortable yet? I promise that we'll get
152
+ you there..
153
+
154
+ But now, let's do something a little uncomfortable. Try using that \
155
+ #{inverse 'reverse'}
156
+ method on a number: #{result '40.reverse'}
157
+ EOF
158
+ test { |res| res.is_a?(NoMethodError) }
159
+ end
160
+
161
+ challenge do
162
+ help <<-EOF
163
+ #{title 'Stop, You\'re Barking Mad!'}
164
+
165
+ You can't reverse the number #{inverse '40'}. I guess you can hold your \
166
+ monitor up to the
167
+ mirror, but reversing a number just doesn't make sense! Ruby has tossed you
168
+ a useful error message.
169
+
170
+ The message is telling you that there is no method #{inverse 'reverse'} for \
171
+ number values in
172
+ Ruby!
173
+
174
+ But, hmm...maybe if you can turn it into a string. Try this: \
175
+ #{result '40.to_s.reverse'}.
176
+ EOF
177
+ test { |res| res == '04' }
178
+ end
179
+
180
+ challenge do
181
+ help <<-EOF
182
+ #{title 'Boys are Different From Girls'}
183
+
184
+ ...just like numbers are different from strings. While you can use methods on
185
+ any object in Ruby, some methods only work on certain types of values. A really
186
+ cool thing about Ruby is that you can always convert between different types
187
+ using Ruby's "to" methods.
188
+
189
+ * #{bold 'to_s'} converts values to strings.
190
+ * #{bold 'to_i'} converts values to integers (numbers.)
191
+ * #{bold 'to_a'} converts values to arrays.
192
+
193
+ What in the world are arrays, you might ask?! They are simply lists. Let's make
194
+ an empty one, by typing in a pair of brackets: #{result '[]'}.
195
+ EOF
196
+ test { |res| res == [] }
197
+ end
198
+
199
+ challenge do
200
+ help <<-EOF
201
+ #{title 'Standing in Line'}
202
+
203
+ Great, you built an empty array. Now let's see what else we can do with it.
204
+
205
+ First off, a good thing to know is that arrays store their information in a
206
+ #{bold 'sequence'}. Think of this like standing in line for popcorn. You are \
207
+ behind
208
+ someone and you wouldn't dream of pushing them aside, right? And that guy behind
209
+ you, you've got a close eye on him, too. First come, first serve.
210
+
211
+ Just like that line for popcorn, the order of an array's information will stay
212
+ consistent for you after you build it...well, at least until you modify it.
213
+
214
+ To try building an array with some stuff in it, here's a list of lottery numbers
215
+ for you: #{result '[12, 47, 35]'}. See those commas? They're important!
216
+ EOF
217
+ test { |res| res == [12, 47, 35] }
218
+ end
219
+
220
+ challenge do
221
+ help <<-EOF
222
+ #{title 'One Raises Its Hand'}
223
+
224
+ Sweet, you've got a short list of lottery numbers. Now, what if we wanted to
225
+ know which one is the highest in the array?
226
+
227
+ Try this: #{result '[12, 47, 35].max'}.
228
+ EOF
229
+ test { |res| res == 47 }
230
+ end
231
+
232
+ challenge do
233
+ help <<-EOF
234
+ #{title 'Tucking a List Away'}
235
+
236
+ Good, good. But it would be pretty annoying to have to retype that list every
237
+ time, right?
238
+
239
+ Let's fix that by using a Ruby #{bold 'variable'}, which helps us store \
240
+ important data.
241
+ Each variable has a unique name, so that it can be summoned up whenever we need
242
+ the info it contains.
243
+
244
+ Call your new variable #{inverse 'ticket'} and place your lottery numbers \
245
+ inside it, like so:
246
+ #{result 'ticket = [12, 47, 35]'}. That equal sign you see is what assigns \
247
+ your array to the
248
+ new variable.
249
+ EOF
250
+ test { |_, vars| vars[:ticket] == [12, 47, 35] }
251
+ end
252
+
253
+ challenge do
254
+ help <<-EOF
255
+ #{title 'Now Type Ticket'}
256
+
257
+ Sweet, now check this out. Type: #{result 'ticket'}
258
+ EOF
259
+ test { |res| res == [12, 47, 35] }
260
+ setup { |vars| vars[:ticket] = [12, 47, 35] }
261
+ end
262
+
263
+ challenge do
264
+ help <<-EOF
265
+ #{title 'Saved, Tucked Away'}
266
+
267
+ Fantastic! You've hung on to your lotto numbers, tucking them away inside a
268
+ #{bold 'variable'} called #{inverse 'ticket'}.
269
+
270
+ Now let's put your lotto numbers in order...sound good? Ruby has a great method
271
+ for that. Use: #{result 'ticket.sort!'}
272
+
273
+ You might notice that the method has an exclamation point at its end. This just
274
+ signals that we intend for Ruby to directly modify the same array that we've
275
+ built, rather than make a brand new copy that is sorted. You'll notice that if
276
+ you try calling #{inverse 'ticket'} again, it will be sorted permanently!
277
+
278
+ When you want to move on, just type #{result 'next'}
279
+ EOF
280
+ end
281
+ end
282
+
283
+ level do
284
+ poem = <<-EOF
285
+ My toast has flown from my hand
286
+ And my toast has gone to the moon.
287
+ But when I saw it on television,
288
+ Planting our flag on Halley's comet,
289
+ More still did I want to eat it.
290
+ EOF
291
+ challenge do
292
+ help <<-EOF
293
+ #{title 'Level #3 is Upon Us'}
294
+
295
+ You built a list. Then you sorted the list. And as you've seen, the \
296
+ #{inverse 'ticket'}
297
+ variable is now changed.
298
+
299
+ Now, let's look at how your second level went down:
300
+
301
+ * #{bold 'Errors'}. If you try to reverse a number or do anything fishy, \
302
+ Ruby will skip
303
+ the prompt and tell you to straighten up.
304
+ * #{bold 'Arrays'} are lists of stored information.
305
+ * #{bold 'Variables'} are a place to save stuff you might need again, as \
306
+ well as give
307
+ that stuff a name. You used the equals sign to do this, in a process called
308
+ assignment.
309
+ Like: #{inverse 'ticket = [14, 37, 18]'}.
310
+
311
+ In all, there are just five levels in this course. You are already two-fifths
312
+ of the way to the end! This is simple stuff, don't you think? More good stuff up
313
+ ahead.
314
+
315
+ Let's change directions for a moment. I've stuffed a bit of poetry for you in a
316
+ certain variable. Take a look, by typing #{result 'print poem'}
317
+ EOF
318
+ setup do |vars|
319
+ vars[:poem] = poem.dup
320
+ end
321
+ test do |_, _, output|
322
+ output == poem
323
+ end
324
+ end
325
+
326
+ challenge do
327
+ help <<-EOF
328
+ #{title 'Sadly, You Hate Toast Poetry'}
329
+
330
+ Look, it's okay. You don't have to like it. You may even want to hack it up.
331
+ Welp, be my guest.
332
+
333
+ Instead of toast, maybe go for a melon or something. Try this one:
334
+ #{result 'poem[\'toast\'] = \'honeydew\''}
335
+ EOF
336
+ setup do |vars|
337
+ vars[:poem] = poem.dup
338
+ end
339
+ test do |_, vars|
340
+ vars[:poem] == poem.sub('toast', 'honeydew')
341
+ end
342
+ end
343
+
344
+ challenge do
345
+ help <<-EOF
346
+ Now type #{result 'print poem'} once again to see the new poem.
347
+
348
+ See how you only changed the first toast? The joke's on you, bread hater.
349
+
350
+ When you want to move on, type #{result 'next'}
351
+ EOF
352
+ setup do |vars|
353
+ vars[:poem] = poem.sub('toast', 'honeydew')
354
+ end
355
+ end
356
+
357
+ challenge do
358
+ help <<-EOF
359
+ #{title 'Ready, Aim'}
360
+
361
+ The square brackets you just used are very common in Ruby. Remember, you typed:
362
+ #{inverse 'poem[\'toast\'] = \'honeydew\''}. That box that holds the word \
363
+ toast uses a square
364
+ bracket on each side. See 'em?
365
+
366
+ The two brackets are like a crosshairs used to line up precisely on a target.
367
+ These brackets mean, "I am looking for ____ somewhere in here." Ready ... aim
368
+ ... #{bold 'data'}. Here, you were looking specifically for toast in order to \
369
+ swap it out
370
+ with a fruit.
371
+
372
+ Let's see if your new experience can help you produce the answer to this
373
+ question: what happens when we reverse this whole poem? #{result 'poem.reverse'}
374
+ EOF
375
+ setup do |vars|
376
+ vars[:poem] = poem.sub('toast', 'honeydew')
377
+ end
378
+ test do |result|
379
+ result == poem.sub('toast', 'honeydew').reverse
380
+ end
381
+ end
382
+
383
+ challenge do
384
+ help <<-EOF
385
+ #{title 'Too Much Reversal'}
386
+
387
+ Okay, I suppose that was expected. The whole poem's been turned backwards,
388
+ letter by letter. But say I really just wanted to reverse the lines only. In
389
+ other words, move the last line up to first and the first line down to last.
390
+ Backwards, yes, but not that backwards.
391
+
392
+ Ruby has a way. Try this: #{result 'poem.lines.to_a.reverse'}
393
+ EOF
394
+ setup do |vars|
395
+ vars[:poem] = poem.sub('toast', 'honeydew')
396
+ end
397
+ test do |result|
398
+ result == poem.sub('toast', 'honeydew').lines.to_a.reverse
399
+ end
400
+ end
401
+
402
+ challenge do
403
+ help <<-EOF
404
+ #{title 'Ringlets of Chained Methods'}
405
+
406
+ So...what actually happened there? You typed \
407
+ #{inverse 'poem.lines.to_a.reverse'} and
408
+ produced some Ruby magic.
409
+
410
+ First, you turned the #{inverse 'poem'} into a list using \
411
+ #{inverse 'lines.to_a'}. The #{inverse 'lines'} component
412
+ decided the way the string should be split up, and then one of our "to" methods,
413
+ #{inverse 'to_a'}, converted those splits into an Array. (to_array.)
414
+
415
+ Different methods, such as #{inverse 'bytes'} and #{inverse 'chars'} can be \
416
+ used in place of #{inverse 'lines'}. By
417
+ using #{inverse 'lines'} here, Ruby split the poem up according to each new \
418
+ line.
419
+
420
+ After that, you #{inverse 'reverse'}'d your Array. You had each line prepared \
421
+ in advance. And
422
+ then you reversed them. That's it!
423
+
424
+ And now, let's tack one more method on the end there, if you don't mind. Try:
425
+ #{result 'print poem.lines.to_a.reverse.join'}.
426
+ EOF
427
+ setup do |vars|
428
+ vars[:poem] = poem.sub('toast', 'honeydew')
429
+ end
430
+ test do |_, _, output|
431
+ output == poem.sub('toast', 'honeydew').lines.to_a.reverse.join
432
+ end
433
+ end
434
+ end
435
+
436
+ level do
437
+ challenge do
438
+ help <<-EOF
439
+ #{title 'Brace Yourselves! Level #4 is Here Now'}
440
+
441
+ Good show, my friend! The join method took that list of reversed lines and put
442
+ them together into a single string. (Sure, you could have also just used to_s.)
443
+
444
+ Time for a quick review.
445
+
446
+ * Exclamation Points. Methods may have exclamation points in their name, which
447
+ just means to impact the current data, rather than making a copy. No big
448
+ deal.
449
+ * Square Brackets. With these, you can target and find things. You can even
450
+ replace them if necessary.
451
+ * Chaining methods lets you get a lot more done in a single command. Break up
452
+ a poem, reverse it, reassemble it: poem.lines.to_a.reverse.join.
453
+
454
+ Guess what? Methods can also have question marks. Try: poem.include? "my hand"
455
+ to check it out.
456
+
457
+ At this point, you may want to tinker with the poem a bit more. A complete list
458
+ of all the String methods is here: http://ruby-doc.org/core/classes/String.html.
459
+ Go ahead and try a few (such as poem.downcase or poem.delete.)
460
+
461
+ And now on to something new. When you're ready to move on, type: books = {}
462
+ EOF
463
+ setup do |vars|
464
+ vars[:poem] = <<-EOF
465
+ My honeydew has flown from my hand
466
+ And my toast has gone to the moon.
467
+ But when I saw it on television,
468
+ Planting our flag on Halley's comet,
469
+ More still did I want to eat it.
470
+ EOF
471
+ end
472
+ test do |_, vars|
473
+ vars[:books] == {}
474
+ end
475
+ end
476
+
477
+ challenge do
478
+ help <<-EOF
479
+ A Wee Blank Book
480
+
481
+ You've made an empty hash, also known as: a dictionary. Hashes store related
482
+ information by giving reusable labels to pieces of our data.
483
+
484
+ We're going to stuff some miniature book reviews in this hash. Here's our rating
485
+ system:
486
+
487
+ * :splendid → a masterpiece.
488
+ * :quite_good → enjoyed, sure, yes.
489
+ * :mediocre → equal parts great and terrible.
490
+ * :quite_not_good → notably bad.
491
+ * :abysmal → steaming wreck.
492
+
493
+ To rate a book, put the title in square brackets and put the rating after the
494
+ equals.
495
+
496
+ For example: books["Gravity's Rainbow"] = :splendid
497
+ EOF
498
+ setup do |vars|
499
+ vars[:books] = {}
500
+ end
501
+ test do |_, vars|
502
+ vars[:books] == { 'Gravity\'s Rainbow' => :splendid }
503
+ end
504
+ end
505
+
506
+ challenge do
507
+ help <<-EOF
508
+ More Bite-Size Reviews
509
+
510
+ Keep going! Fill it up with some useful reviews. And if you want to see the
511
+ whole list, just type: books
512
+
513
+ Again, the available ratings are: :splendid, :quite_good, :mediocre,
514
+ :quite_not_good, and :abysmal.
515
+
516
+ Notice that these ratings are not strings. When you place a colon in front of
517
+ a simple word, you get a Ruby symbol. Symbols are much cheaper than strings (in
518
+ terms of computer memory.) If you need to use a word over and over in your
519
+ program itself, use a symbol. Rather than having thousands of copies of that
520
+ word in memory, the computer will store a symbol only once, and refer to it over
521
+ and over.
522
+
523
+ Once you've got three or four books in there, type: books.length. You should see
524
+ the right amount.
525
+ EOF
526
+ setup do |vars|
527
+ vars[:books] = { 'Gravity\'s Rainbow' => :splendid }
528
+ end
529
+ test do |result, vars|
530
+ vars[:books].length > 2 && result == vars[:books].length
531
+ end
532
+ end
533
+
534
+ challenge do
535
+ help <<-EOF
536
+ Wait, Did I Like Gravity's Rainbow?
537
+
538
+ See, the length method works on strings, list and hashes. One great thing about
539
+ Ruby is that method names are often reused, which means a lot less stuff for you
540
+ to remember.
541
+
542
+ If you'd like to look up one of your old reviews, just put the title of the book
543
+ in the square box again. Leave off the equal sign this time, though, since
544
+ you're not assigning any information. You're just researching!
545
+
546
+ Do it like this: books["Gravity's Rainbow"]
547
+ EOF
548
+ test do |result, vars|
549
+ result == vars[:books]["Gravity's Rainbow"]
550
+ end
551
+ end
552
+
553
+ challenge do
554
+ help <<-EOF
555
+ Hashes as Pairs
556
+
557
+ Keep in mind that hashes won't keep things in order. That's not their job. It'll
558
+ just pair up two things: a key and a value. In your reviews, the key is the
559
+ book's title and the value is the rating, in this case a symbol.
560
+
561
+ If you want to see a nice list of the book titles you've reviewed: books.keys
562
+
563
+ When you want to move on, type next
564
+ EOF
565
+ end
566
+
567
+ challenge do
568
+ help <<-EOF
569
+ Are You Harsh?
570
+
571
+ So are you giving out harsh, unfair reviews? Let's keep score with this hash:
572
+ ratings = Hash.new(0)
573
+ EOF
574
+ test do |_, vars|
575
+ vars[:ratings] == Hash.new(0)
576
+ end
577
+ end
578
+
579
+ challenge do
580
+ help <<-EOF
581
+ Are You Harsh?
582
+
583
+ That command was another way to build an empty hash. The zero you passed in will
584
+ set all of your initial rating counts to zero.
585
+
586
+ Okay, now let's count up your reviews. Stay with me on this one.
587
+
588
+ Type: books.values.each { |rate| ratings[rate] += 1 }
589
+
590
+ (That | in the code is called the pipe character. It's probably located right
591
+ above the Enter key on your keyboard.)
592
+
593
+ This code will turn all your unique values in books...into keys within the new
594
+ ratings hash. Crazy, right? Then, as it looks at each rating you originally gave
595
+ in books, it will increase the count value for that rating in ratings
596
+
597
+ After you've build your new hash of count values, type ratings again to see the
598
+ full tally. This new hash will show you a rating followed by the number of times
599
+ you've given that rating.
600
+
601
+ When you want to move on, type next
602
+ EOF
603
+ setup do |vars|
604
+ vars[:ratings] = Hash.new(0)
605
+ vars[:books] = {
606
+ 'Gravity\'s Rainbow' => :splendid
607
+ } unless vars[:books].is_a?(Hash) && vars[:books].length > 0
608
+ end
609
+ end
610
+
611
+ challenge do
612
+ help <<-EOF
613
+ A Tally
614
+
615
+ One of the amazing new things we've just used is called a block. Basically,
616
+ a block is a chunk of Ruby code surrounded by curly braces. We'll take a closer
617
+ look at them later.
618
+
619
+ But for now, let's try another block:
620
+
621
+ 5.times { print "Odelay!" }
622
+
623
+ When you want to move on, type next. You want the badge, don't you?
624
+ EOF
625
+ end
626
+ end
627
+
628
+ level do
629
+ comics = <<-EOF
630
+ Achewood: http://achewood.com/
631
+ Dinosaur Comics: http://qwantz.com/
632
+ Perry Bible Fellowship: http://cheston.com/pbf/archive.html
633
+ Get Your War On: http://mnftiu.cc/
634
+ EOF
635
+ new_comics = comics + 'Cat and Girl: http://catandgirl.com/'
636
+
637
+ challenge do
638
+ help <<-EOF
639
+ Now Arriving at Level #5
640
+
641
+ Blocks are always attached to methods. You saw this with the times method, which
642
+ took the block and ran its code over and over. (In this case: five times.)
643
+
644
+ This last lesson was a bit longer. You've probably used up three minutes
645
+ learning about:
646
+
647
+ * Hashes. The little 'dictionary' with the curly braces: {}.
648
+ * Symbols. Tiny, efficiently reusable code words with a colon: :splendid.
649
+ * Blocks. Chunks of code which can be tacked on to many of Ruby's methods.
650
+ Here's the code you used to build a scorecard:
651
+ books.values.each { |rate| ratings[rate] += 1 }.
652
+
653
+ On to the next thing, okay? On your computer, you probably have a lot of
654
+ different files. Some files have pictures in them, some have programs in them.
655
+ And files are often organized into folders, also called: directories.
656
+
657
+ I've prepared a few directories for you. Take a look, using the following
658
+ command: Dir.entries "/"
659
+ EOF
660
+ test do |result|
661
+ result == Dir.entries('/')
662
+ end
663
+ end
664
+
665
+ challenge do
666
+ help <<-EOF
667
+ The Private Collection of Dr. Dir
668
+
669
+ You've just listed out everything in the top directory, which is called the
670
+ root. It's indicated by the single slash in your string parameter. It contains
671
+ some programs, as well as other tutorials and such.
672
+
673
+ So, what exactly is that Dir.entries method? Well, it's just a method, like the
674
+ others you've seen. Dir has a collection of methods for checking out file
675
+ directories, and entries is being called on the Dir variable. The entries method
676
+ just lists everything in the directory you've indicated!
677
+
678
+ One other little thing we haven't really talked about quite yet: method
679
+ arguments. A few are highlighted below.
680
+
681
+ * Dir.entries "/" -- Anything listed after a method is considered an
682
+ 'attachment'.
683
+ * print poem -- See, print is just an ordinary method, while the poem is what
684
+ got attached for printing.
685
+ * print "pre", "event", "ual", "ism" -- This bit has several arguments! Ruby
686
+ makes us use commas to distinguish between them.
687
+
688
+ Next up, we'll list just the text files in our directory using a bracket
689
+ notation. Remember how it searches?
690
+
691
+ Try: Dir["*.txt"]
692
+ EOF
693
+ setup do
694
+ File.write('comics.txt', comics)
695
+ end
696
+ test do |result|
697
+ result == Dir['*.txt']
698
+ end
699
+ end
700
+
701
+ challenge do
702
+ help <<-EOF
703
+ Come, Read Comics With Me
704
+
705
+ The Dir[] syntax is kind of like entries, but instead searches for files with
706
+ wildcard characters.
707
+
708
+ Here, we see those square brackets again! Notice how they still mean, "I am
709
+ looking for _____?"
710
+
711
+ Dir["*.txt"] says to Ruby: "I am looking for any files which end with .txt."
712
+ The asterisk indicates the "any file" part. Ruby then hands us every file that
713
+ matches our request.
714
+
715
+ Alright, let's crack open this comics file, then! We'll use a new method to do
716
+ it.
717
+
718
+ Here's the way: print File.read("comics.txt")
719
+ EOF
720
+ test do |_, _, output|
721
+ output == File.read('comics.txt')
722
+ end
723
+ end
724
+
725
+ challenge do
726
+ help <<-EOF
727
+ Mi Comicas, Tu Comicas
728
+
729
+ Alright, then! Now we can start to use files to store things. This is great
730
+ because normally when we exit Ruby, all our variables will be gone. Ruby, by
731
+ itself, forgets these things. But if we save things in files, we can read those
732
+ files in future Ruby escapades.
733
+
734
+ Hey, and guess what? The Home directory is yours now! I gave it to you! Am I
735
+ generous or what?!
736
+
737
+ First thing we'll do is make a copy of the comics file and put in new folder
738
+ called 'Home'.
739
+
740
+ To do that, you'll want to use a copying method called cp on a variable called
741
+ FileUtils.
742
+
743
+ Use FileUtils.cp('comics.txt', 'Home/comics.txt')
744
+ EOF
745
+ setup do
746
+ Dir.mkdir('Home') unless Dir.exist?('Home')
747
+ end
748
+ test do
749
+ File.exist?('Home/comics.txt') && \
750
+ File.read('Home/comics.txt') == comics
751
+ end
752
+ end
753
+
754
+ challenge do
755
+ help <<-EOF
756
+ Okay, you've got a copy, and it's located in the right directory. Check it out!
757
+
758
+ Use Dir["Home/*.txt"]
759
+
760
+ Type next to move to the next lesson when you're finished.
761
+ EOF
762
+ setup do
763
+ File.write('Home/comics.txt', comics)
764
+ end
765
+ end
766
+
767
+ challenge do
768
+ help <<-EOF
769
+ Your Own Turf
770
+
771
+ To add your own comic to the list, let's open the file in append mode,
772
+ which we indicate with the "a" parameter. This will allow us to put new stuff
773
+ at the end of the file.
774
+
775
+ Start by entering this code: File.open("Home/comics.txt", "a") do |f|
776
+
777
+ And Now For the Startling Conclusion
778
+
779
+ So your prompt has changed, see that? Your prompt is a double dot now.
780
+
781
+ In this tutorial, this prompt means that Ruby is expecting you to type a little
782
+ bit more. As you write further lines of Ruby code, the double-dots will continue
783
+ until the tutorial sees you are completely finished.
784
+
785
+ Alright, so here's more code. You've already typed the first line, so just enter
786
+ the second line.
787
+
788
+ File.open("Home/comics.txt", "a") do |f|
789
+ f << "Cat and Girl: http://catandgirl.com/"
790
+ end
791
+
792
+ Ruby Sits Still
793
+
794
+ The last line will add the Cat and Girl comic to the list, but Ruby's going to
795
+ wait until you're totally finished to take action.
796
+
797
+ This means we'll also need to wrap up the code block you've started. Turns out,
798
+ you actually opened a new block when you typed that do keyword.
799
+
800
+ So far the blocks we've seen have used curly braces, but this time we'll be
801
+ using do and end instead. A lot of Rubyists will use a do...end setup when the
802
+ block goes on for many lines.
803
+
804
+ Let's get that block finished now, with your very own end.
805
+ EOF
806
+ test do
807
+ File.exist?('Home/comics.txt') && \
808
+ File.read('Home/comics.txt') == new_comics
809
+ end
810
+ end
811
+
812
+ challenge do
813
+ help <<-EOF
814
+ Ruby Sits Still
815
+
816
+ Sweet! You've added that brand new comic to the end of the file. You can see for
817
+ yourself, using the read method you saw earlier:
818
+ print File.read("Home/comics.txt").
819
+
820
+ When you want to move on to the next lesson, type next.
821
+ EOF
822
+ setup do
823
+ File.write('Home/comics.txt', new_comics)
824
+ end
825
+ end
826
+
827
+ challenge do
828
+ help <<-EOF
829
+ The Clock Nailed To the File
830
+
831
+ I wonder, what time was it when you changed your file? We can check that out.
832
+
833
+ Type: File.mtime("Home/comics.txt").
834
+ EOF
835
+ test do |result|
836
+ result == File.mtime('Home/comics.txt')
837
+ end
838
+ end
839
+
840
+ challenge do
841
+ help <<-EOF
842
+ Just the Hour Hand
843
+
844
+ Excellent, there's the exact time, precisely when you added to the file.
845
+ The mtime method gives you a nice Ruby Time object.
846
+
847
+ If you want to check just what hour it was, hit the up arrow key to put your
848
+ previous entry in the console. Then modify the line to say:
849
+ File.mtime("Home/comics.txt").hour.
850
+ EOF
851
+ test do |result|
852
+ result == File.mtime('Home/comics.txt').hour
853
+ end
854
+ end
855
+
856
+ end
857
+ end
858
+ end