gopher2000 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rvmrc +1 -0
- data/Gemfile +27 -0
- data/LICENSE.txt +14 -0
- data/README.markdown +344 -0
- data/Rakefile +38 -0
- data/bin/gopher2000 +51 -0
- data/examples/default_route.rb +22 -0
- data/examples/nyan.rb +62 -0
- data/examples/simple.rb +147 -0
- data/examples/twitter.rb +61 -0
- data/examples/weather.rb +69 -0
- data/gopher2000.gemspec +35 -0
- data/lib/gopher2000/base.rb +552 -0
- data/lib/gopher2000/dispatcher.rb +81 -0
- data/lib/gopher2000/dsl.rb +128 -0
- data/lib/gopher2000/errors.rb +14 -0
- data/lib/gopher2000/handlers/base_handler.rb +18 -0
- data/lib/gopher2000/handlers/directory_handler.rb +125 -0
- data/lib/gopher2000/rendering/abstract_renderer.rb +10 -0
- data/lib/gopher2000/rendering/base.rb +174 -0
- data/lib/gopher2000/rendering/menu.rb +129 -0
- data/lib/gopher2000/rendering/text.rb +10 -0
- data/lib/gopher2000/request.rb +21 -0
- data/lib/gopher2000/response.rb +25 -0
- data/lib/gopher2000/server.rb +85 -0
- data/lib/gopher2000/version.rb +4 -0
- data/lib/gopher2000.rb +33 -0
- data/scripts/god.rb +8 -0
- data/spec/application_spec.rb +54 -0
- data/spec/dispatching_spec.rb +144 -0
- data/spec/dsl_spec.rb +116 -0
- data/spec/gopher_spec.rb +1 -0
- data/spec/handlers/directory_handler_spec.rb +116 -0
- data/spec/helpers_spec.rb +16 -0
- data/spec/rendering/base_spec.rb +59 -0
- data/spec/rendering/menu_spec.rb +109 -0
- data/spec/rendering_spec.rb +84 -0
- data/spec/request_spec.rb +30 -0
- data/spec/response_spec.rb +33 -0
- data/spec/routing_spec.rb +92 -0
- data/spec/sandbox/old/socks.txt +0 -0
- data/spec/sandbox/socks.txt +0 -0
- data/spec/server_spec.rb +127 -0
- data/spec/spec_helper.rb +52 -0
- data/specs.watchr +60 -0
- metadata +211 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.2-head@gopherpedia
|
data/Gemfile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in gopher.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem "rake"
|
7
|
+
gem "logging"
|
8
|
+
|
9
|
+
# Add dependencies to develop your gem here.
|
10
|
+
# Include everything needed to run rake, tests, features, etc.
|
11
|
+
group :development do
|
12
|
+
gem 'simplecov', :require => false, :group => :test
|
13
|
+
|
14
|
+
gem "shoulda", ">= 0"
|
15
|
+
gem "rspec"
|
16
|
+
|
17
|
+
gem "bundler", "~> 1.0.0"
|
18
|
+
gem "watchr"
|
19
|
+
|
20
|
+
# There's a god example script stashed away in the repo
|
21
|
+
gem "god"
|
22
|
+
|
23
|
+
#
|
24
|
+
# gems used in examples and for development.
|
25
|
+
#
|
26
|
+
gem "weather-underground"
|
27
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
2
|
+
Version 2, December 2004
|
3
|
+
|
4
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
5
|
+
|
6
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
7
|
+
copies of this license document, and changing it is allowed as long
|
8
|
+
as the name is changed.
|
9
|
+
|
10
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
11
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
12
|
+
|
13
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
14
|
+
|
data/README.markdown
ADDED
@@ -0,0 +1,344 @@
|
|
1
|
+
It's...
|
2
|
+
|
3
|
+
_____ _ _____ _____ _____ _____
|
4
|
+
| __ \ | | / __ \| _ || _ || _ |
|
5
|
+
| | \/ ___ _ __ | |__ ___ _ __ `' / /'| |/' || |/' || |/' |
|
6
|
+
| | __ / _ \| '_ \| '_ \ / _ \ '__| / / | /| || /| || /| |
|
7
|
+
| |_\ \ (_) | |_) | | | | __/ | ./ /___\ |_/ /\ |_/ /\ |_/ /
|
8
|
+
\____/\___/| .__/|_| |_|\___|_| \_____/ \___/ \___/ \___/
|
9
|
+
| |
|
10
|
+
|_|
|
11
|
+
|
12
|
+
|
13
|
+
Gopher2000 - A Gopher server for the next millenium
|
14
|
+
===================================================
|
15
|
+
|
16
|
+
Gopher2000 is a ruby-based Gopher server. It is built for speedy, enjoyable development of
|
17
|
+
all sorts of gopher sites.
|
18
|
+
|
19
|
+
Features
|
20
|
+
--------
|
21
|
+
* Simple, Sintra-inspired routing DSL.
|
22
|
+
* Dynamic requests via named parameters on request paths.
|
23
|
+
* built on Event Machine.
|
24
|
+
* Easy to mount directories and serve up files.
|
25
|
+
* built in logging and stats.
|
26
|
+
* Runs on Ruby 1.9.2 with all the modern conveniences.
|
27
|
+
|
28
|
+
Requirements
|
29
|
+
------------
|
30
|
+
* Ruby 1.9.2 or greater
|
31
|
+
* Nerves of steel
|
32
|
+
|
33
|
+
Examples
|
34
|
+
--------
|
35
|
+
|
36
|
+
Writing a functional Gopher app is as simple as:
|
37
|
+
|
38
|
+
```rb
|
39
|
+
route '/simple' do
|
40
|
+
"hi" # You can output any text you want here
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
|
45
|
+
Or, if you want to provide more interactivity, you can do something like:
|
46
|
+
|
47
|
+
```rb
|
48
|
+
route '/' do
|
49
|
+
render :index
|
50
|
+
end
|
51
|
+
|
52
|
+
menu :index do
|
53
|
+
# output a text entry in the menu
|
54
|
+
text 'simple gopher example'
|
55
|
+
|
56
|
+
# use br(x) to add x space between lines
|
57
|
+
br(2)
|
58
|
+
|
59
|
+
# link somewhere
|
60
|
+
link 'current time', '/time'
|
61
|
+
br
|
62
|
+
end
|
63
|
+
|
64
|
+
route '/time' do
|
65
|
+
"It is currently #{Time.now}"
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
You can see more working examples in the examples/ folder
|
70
|
+
|
71
|
+
Running a script
|
72
|
+
----------------
|
73
|
+
|
74
|
+
You can use the supplied wrapper script
|
75
|
+
|
76
|
+
```
|
77
|
+
gopher2000 -d examples/simple.rb
|
78
|
+
|
79
|
+
==> *start server at 0.0.0.0 7070*
|
80
|
+
```
|
81
|
+
|
82
|
+
Or, if you include gopher in your file, you can just run the script itself:
|
83
|
+
|
84
|
+
```rb
|
85
|
+
# scriptname.rb
|
86
|
+
require 'gopher2000'
|
87
|
+
|
88
|
+
# ...
|
89
|
+
# write some code here
|
90
|
+
# ...
|
91
|
+
|
92
|
+
# Then, run 'ruby scriptname.rb'
|
93
|
+
|
94
|
+
==> *start server at 0.0.0.0 7070*
|
95
|
+
```
|
96
|
+
There are several command-line options:
|
97
|
+
|
98
|
+
* -d -- run in debug mode
|
99
|
+
* -p [port] -- which port to listen on
|
100
|
+
* -o [addr] -- what IP/host to listen on
|
101
|
+
* -e [env] -- what 'environment' to use -- this isn't really used by
|
102
|
+
Gopher2000, but you could use it when writing your app to determine
|
103
|
+
how you behave in production vs development, etc.
|
104
|
+
|
105
|
+
Command line options will override defaults specified in your script
|
106
|
+
-- so you can try out things on a different port/address if needed.
|
107
|
+
|
108
|
+
|
109
|
+
Developing Gopher Sites
|
110
|
+
-----------------------
|
111
|
+
|
112
|
+
Gopher2000 makes developing sites easy! Any time you change your
|
113
|
+
script, Gopher2000 will reload it. This way, you can make tweaks and
|
114
|
+
your site will be refreshed immediately. NOTE -- this is an
|
115
|
+
experimental feature, and might need some work.
|
116
|
+
|
117
|
+
Serving Files and Directories
|
118
|
+
-----------------------------
|
119
|
+
|
120
|
+
If you just want to serve up some files, there's a command for that:
|
121
|
+
|
122
|
+
```rb
|
123
|
+
mount '/files' => '/home/username/files', :filter => '*.jpg'
|
124
|
+
```
|
125
|
+
|
126
|
+
This will display a list of all the JPGs in the files directory.
|
127
|
+
|
128
|
+
Outputting Gopher Menus
|
129
|
+
-----------------------
|
130
|
+
|
131
|
+
There are a collection of commands to output Gopher menus (see
|
132
|
+
rendering/menu.rb for the code). The commands are:
|
133
|
+
|
134
|
+
**line(type, text, selector)** - output a line of type 'type' -- see
|
135
|
+
the RFC for the different types of links you can have.
|
136
|
+
|
137
|
+
**text** - output a line of text with no action on it.
|
138
|
+
|
139
|
+
**br(x)** - output x blank lines.
|
140
|
+
|
141
|
+
**error** - output an error message.
|
142
|
+
|
143
|
+
**directory** - (aliased as menu) output a link to a 'directory' -- this could be an
|
144
|
+
actual directory if you're building some sort of filesystem tree, or
|
145
|
+
a sub-menu for other actions in your app.
|
146
|
+
|
147
|
+
**link(text, selector)** - output a menu link to to the /selector path.
|
148
|
+
|
149
|
+
**search(text, selector)** -- output a link to a search action at
|
150
|
+
/selector.
|
151
|
+
|
152
|
+
Outputting Text
|
153
|
+
---------------
|
154
|
+
|
155
|
+
If you would like to output text, but have the ability to format it
|
156
|
+
nicely, you can use a 'text' block like this:
|
157
|
+
|
158
|
+
```rb
|
159
|
+
route '/prettytext' do
|
160
|
+
render :prettytext
|
161
|
+
end
|
162
|
+
|
163
|
+
#
|
164
|
+
# special text output rendering
|
165
|
+
#
|
166
|
+
text :prettytext do
|
167
|
+
@text = "A really long chunk of text. Lorem ipsum dolor sit amet ... nec massa."
|
168
|
+
|
169
|
+
# nicely wrapped text
|
170
|
+
block @text
|
171
|
+
|
172
|
+
# spacing
|
173
|
+
br(2)
|
174
|
+
|
175
|
+
# smaller line-width
|
176
|
+
block @text, 30
|
177
|
+
end
|
178
|
+
|
179
|
+
```
|
180
|
+
|
181
|
+
A call to:
|
182
|
+
|
183
|
+
```
|
184
|
+
echo "/prettytext" | ncat -C localhost 7070
|
185
|
+
```
|
186
|
+
|
187
|
+
Will return your text, but with nice wrapping, etc.
|
188
|
+
|
189
|
+
@todo headers, etc.
|
190
|
+
|
191
|
+
|
192
|
+
Making It Pretty
|
193
|
+
----------------
|
194
|
+
|
195
|
+
There are several helpers which you can call within render blocks to
|
196
|
+
help make your output a little shinier:
|
197
|
+
|
198
|
+
**width(x)** will set the width of your output. The default is 80
|
199
|
+
characters. You can change this to make your output wider or
|
200
|
+
thinner. This setting is used by **block** and also by the methods
|
201
|
+
described below.
|
202
|
+
|
203
|
+
**header(text, style='=')** will generate a very simple 'header', which is
|
204
|
+
basically the text you specify with an underline of the character
|
205
|
+
you specify. It will be centered in your output width, and will look
|
206
|
+
something like this:
|
207
|
+
|
208
|
+
Hello There!
|
209
|
+
=====================
|
210
|
+
|
211
|
+
**big_header** is the same as header, except it is bigger and better!
|
212
|
+
|
213
|
+
=====================
|
214
|
+
= Hello There! =
|
215
|
+
=====================
|
216
|
+
|
217
|
+
**underline** can be used to just output plain old lines, if you're
|
218
|
+
into that sort of thing.
|
219
|
+
|
220
|
+
|
221
|
+
Testing
|
222
|
+
-------
|
223
|
+
|
224
|
+
Here's some simple ways to test your server. First, you can always
|
225
|
+
just install a
|
226
|
+
[gopher client](http://lmgtfy.com/?q=gopher+clients). Or, if you like
|
227
|
+
to live on the edge, there's a few commands worth learning. First, you
|
228
|
+
can use [netcat](http://netcat.sourceforge.net/) to achieve
|
229
|
+
awesomeness. Here's some examples, assuming you're running the example
|
230
|
+
script on port 7070:
|
231
|
+
|
232
|
+
|
233
|
+
```
|
234
|
+
#
|
235
|
+
# getting a menu listing
|
236
|
+
#
|
237
|
+
|
238
|
+
~/Projects/gopher2000: echo "/" | nc localhost 7070
|
239
|
+
isimple gopher example null (FALSE) 0
|
240
|
+
i null (FALSE) 0
|
241
|
+
i null (FALSE) 0
|
242
|
+
0current time /time 0.0.0.0 7070
|
243
|
+
i null (FALSE) 0
|
244
|
+
0about /about 0.0.0.0 7070
|
245
|
+
i null (FALSE) 0
|
246
|
+
7Hey, what is your name? /hello 0.0.0.0 7070
|
247
|
+
i null (FALSE) 0
|
248
|
+
7echo test /echo_test 0.0.0.0 7070
|
249
|
+
i null (FALSE) 0
|
250
|
+
1filez /files 0.0.0.0 7070
|
251
|
+
|
252
|
+
#
|
253
|
+
# getting a simple text response
|
254
|
+
#
|
255
|
+
~/Projects/gopher2000: echo "/about" | nc localhost 7070
|
256
|
+
Gopher 2000 -- World Domination via Text Protocols
|
257
|
+
.
|
258
|
+
```
|
259
|
+
|
260
|
+
|
261
|
+
Or, you can use the equally awesome [ncat](http://nmap.org/ncat/),
|
262
|
+
which is basically the successor to netcat. In general, I find that
|
263
|
+
ncat works better, particularly if you're using non-blocking
|
264
|
+
operations. Here's an example of it in operation:
|
265
|
+
|
266
|
+
|
267
|
+
```
|
268
|
+
#
|
269
|
+
# Testing text output
|
270
|
+
#
|
271
|
+
|
272
|
+
~/Projects/gopher2000: echo "/about" | ncat -C localhost 7070
|
273
|
+
Gopher 2000 -- World Domination via Text Protocols
|
274
|
+
.
|
275
|
+
|
276
|
+
#
|
277
|
+
# testing a route with some input
|
278
|
+
#
|
279
|
+
|
280
|
+
~/Projects/gopher2000: echo "/hello\tcolin" | ncat -C localhost 7070
|
281
|
+
iHello, colin! null (FALSE) 0
|
282
|
+
|
283
|
+
.
|
284
|
+
|
285
|
+
```
|
286
|
+
|
287
|
+
|
288
|
+
|
289
|
+
Logging
|
290
|
+
-------
|
291
|
+
|
292
|
+
Logging is pretty basic at the moment. Right now debug messages are
|
293
|
+
dumped to stderr. There's also an apache-esque access log, which can
|
294
|
+
be written to a file specified like this:
|
295
|
+
|
296
|
+
```rb
|
297
|
+
set :access_log, "/tmp/access.log"
|
298
|
+
```
|
299
|
+
|
300
|
+
The log will rollover daily, so your million hits per day won't
|
301
|
+
accumulate into an unmanageable file.
|
302
|
+
|
303
|
+
The format is a pretty basic tab-delimited file:
|
304
|
+
|
305
|
+
timestamp ip_address request_url result_code response_size
|
306
|
+
2012-04-05 19:14:01 127.0.0.1 /lookup success 46
|
307
|
+
|
308
|
+
Non-Blocking Requests
|
309
|
+
---------------------
|
310
|
+
|
311
|
+
When not running in debug mode, Gopher2000 will handle requests
|
312
|
+
without blocking -- this way, if you have an app that handles slow
|
313
|
+
requests, your users aren't held up waiting for other requests to
|
314
|
+
finish. However, this is somewhat experimental, so you can turn it off
|
315
|
+
by setting :non_blocking to be false in your script:
|
316
|
+
|
317
|
+
```rb
|
318
|
+
set :non_blocking, false
|
319
|
+
```
|
320
|
+
|
321
|
+
Also, non-blocking is always off in debug mode.
|
322
|
+
|
323
|
+
You probably need to be wary of this feature if you're actually
|
324
|
+
running a Gopher server that needs to be non-blocking. Read up on
|
325
|
+
EventMachine's defer feature if you need to learn more.
|
326
|
+
|
327
|
+
|
328
|
+
TODO
|
329
|
+
----
|
330
|
+
* More examples
|
331
|
+
* Work on putting routing/rendering/etc into same context, and making
|
332
|
+
instance variables/methods generally available.
|
333
|
+
* Documentation
|
334
|
+
* clean up/improve EventMachine usage
|
335
|
+
* stats generation
|
336
|
+
|
337
|
+
References
|
338
|
+
----------
|
339
|
+
|
340
|
+
* http://www.ietf.org/rfc/rfc1436.txt -- the original RFC for the
|
341
|
+
Gopher Protocol
|
342
|
+
* https://github.com/sinatra/sinatra -- almost everything good in this
|
343
|
+
library was taken or influenced by something in Sinatra. RUN don't
|
344
|
+
walk to the code and take a look.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup :default, :test, :development
|
3
|
+
|
4
|
+
require "bundler/gem_tasks"
|
5
|
+
require 'rdoc/task'
|
6
|
+
|
7
|
+
require "gopher2000/version"
|
8
|
+
|
9
|
+
require 'rspec/core'
|
10
|
+
require 'rspec/core/rake_task'
|
11
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
12
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
16
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
17
|
+
spec.rcov_opts = %w{--exclude .bundler,.rvm}
|
18
|
+
spec.rcov = true
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :spec
|
22
|
+
|
23
|
+
Bundler::GemHelper.install_tasks
|
24
|
+
|
25
|
+
begin
|
26
|
+
require 'yard'
|
27
|
+
YARD_OPTS = ['-m', 'markdown', '-M', 'redcarpet']
|
28
|
+
DOC_FILES = ['lib/**/*.rb', 'README.markdown']
|
29
|
+
|
30
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
31
|
+
t.files = DOC_FILES
|
32
|
+
#t.options = YARD_OPTS
|
33
|
+
|
34
|
+
puts t.inspect
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
puts "You need to install YARD."
|
38
|
+
end
|
data/bin/gopher2000
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# get rid of errors when running via bundle
|
5
|
+
# @see https://github.com/ddollar/foreman/issues/94
|
6
|
+
#
|
7
|
+
$stdout.sync = true
|
8
|
+
|
9
|
+
require 'optparse'
|
10
|
+
require 'gopher2000'
|
11
|
+
|
12
|
+
#
|
13
|
+
# pull in any arguments and set them as env variables
|
14
|
+
#
|
15
|
+
opts = OptionParser.new
|
16
|
+
opts.banner = <<-EOS
|
17
|
+
|
18
|
+
Run a gopher server!
|
19
|
+
|
20
|
+
Usage: #{File.basename($0)} [options] [scriptname]
|
21
|
+
|
22
|
+
EOS
|
23
|
+
|
24
|
+
opts.separator ""
|
25
|
+
opts.separator "Options:"
|
26
|
+
|
27
|
+
params = {
|
28
|
+
:debug => false,
|
29
|
+
:env => :production
|
30
|
+
}
|
31
|
+
|
32
|
+
opts.on('-d', '--debug', "run in debug mode") { params[:debug] = true }
|
33
|
+
opts.on('-p port', 'set the port (default is 70)') { |val| params[:port] = Integer(val) }
|
34
|
+
opts.on('-o addr', 'set the host (default is 0.0.0.0)') { |val| params[host] = val }
|
35
|
+
opts.on('-e env', 'set the environment (default is development)') { |val| params[:env] = val.to_sym }
|
36
|
+
|
37
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
38
|
+
puts opts
|
39
|
+
exit!
|
40
|
+
end
|
41
|
+
|
42
|
+
extra = opts.parse!(ARGV)
|
43
|
+
|
44
|
+
script = extra.shift
|
45
|
+
|
46
|
+
if script.nil?
|
47
|
+
puts "Sorry, you need to specify a script to run"
|
48
|
+
exit!
|
49
|
+
end
|
50
|
+
|
51
|
+
run script, params
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
#
|
5
|
+
# Simple gopher example
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'gopher2000'
|
9
|
+
|
10
|
+
set :host, '0.0.0.0'
|
11
|
+
set :port, 7070
|
12
|
+
|
13
|
+
# you can specify a destination for access log, for stats/etc
|
14
|
+
set :access_log, "/tmp/access.log"
|
15
|
+
|
16
|
+
route '/gopher' do
|
17
|
+
"Greetings from Gopher 2000!" # You can output any text you want here
|
18
|
+
end
|
19
|
+
|
20
|
+
default_route do
|
21
|
+
"I AM HERE"
|
22
|
+
end
|
data/examples/nyan.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
#
|
5
|
+
# NYAN cat in gopherspace
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'gopher2000'
|
9
|
+
|
10
|
+
set :host, '0.0.0.0'
|
11
|
+
set :port, 7070
|
12
|
+
|
13
|
+
route '/nyan' do
|
14
|
+
|
15
|
+
@cats = [
|
16
|
+
" + o + o
|
17
|
+
+ o + +
|
18
|
+
o +
|
19
|
+
o + + +
|
20
|
+
+ o o + o
|
21
|
+
-_-_-_-_-_-_-_,------, o
|
22
|
+
_-_-_-_-_-_-_-| /\\_/\\
|
23
|
+
-_-_-_-_-_-_-~|__( ^ .^) + +
|
24
|
+
_-_-_-_-_-_-_-\"\" \"\"
|
25
|
+
+ o o + o
|
26
|
+
+ +
|
27
|
+
o o o o +
|
28
|
+
o +
|
29
|
+
+ + o o + ",
|
30
|
+
|
31
|
+
" o + + +
|
32
|
+
o + o
|
33
|
+
o +
|
34
|
+
+ o + o
|
35
|
+
o + o +
|
36
|
+
_-_-_-_-_-_-_-,------, o +
|
37
|
+
-_-_-_-_-_-_-_| /\\_/\\ +
|
38
|
+
_-_-_-_-_-_-_~|__( ^ .^) o
|
39
|
+
-_-_-_-_-_-_-_ \"\" \"\"
|
40
|
+
+ + o o +
|
41
|
+
o + o +
|
42
|
+
+ o + + o
|
43
|
+
+ +
|
44
|
+
+ o + ",
|
45
|
+
|
46
|
+
"░░▓▓░░░░░░░░▓▓░░
|
47
|
+
░▓▒▒▓░░░░░░▓▒▒▓░
|
48
|
+
░▓▒▒▒▓░░░░▓▒▒▒▓░
|
49
|
+
░▓▒▒▒▒▓▓▓▓▒▒▒▒▓░
|
50
|
+
░▓▒▒▒▒▒▒▒▒▒▒▒▒▒▓
|
51
|
+
▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓
|
52
|
+
▓▒▒▒░▓▒▒▒▒▒░▓▒▒▓
|
53
|
+
▓▒▒▒▓▓▒▒▒▓▒▓▓▒▒▓
|
54
|
+
▓▒░░▒▒▒▒▒▒▒▒▒░░▓
|
55
|
+
▓▒░░▒▓▒▒▓▒▒▓▒░░▓
|
56
|
+
░▓▒▒▒▓▓▓▓▓▓▓▒▒▓░
|
57
|
+
░░▓▒▒▒▒▒▒▒▒▒▒▓░░
|
58
|
+
░░░▓▓▓▓▓▓▓▓▓▓░░░"
|
59
|
+
]
|
60
|
+
|
61
|
+
@cats.sample
|
62
|
+
end
|