sinatra 1.0 → 1.1.a

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sinatra might be problematic. Click here for more details.

Files changed (57) hide show
  1. data/CHANGES +108 -1
  2. data/LICENSE +1 -1
  3. data/README.de.rdoc +1024 -0
  4. data/README.es.rdoc +1047 -0
  5. data/README.fr.rdoc +1038 -0
  6. data/README.hu.rdoc +607 -0
  7. data/README.jp.rdoc +473 -15
  8. data/README.rdoc +429 -41
  9. data/Rakefile +17 -6
  10. data/lib/sinatra/base.rb +357 -158
  11. data/lib/sinatra/showexceptions.rb +9 -1
  12. data/sinatra.gemspec +52 -9
  13. data/test/builder_test.rb +25 -1
  14. data/test/coffee_test.rb +88 -0
  15. data/test/encoding_test.rb +18 -0
  16. data/test/filter_test.rb +61 -2
  17. data/test/hello.mab +1 -0
  18. data/test/helper.rb +1 -0
  19. data/test/helpers_test.rb +141 -37
  20. data/test/less_test.rb +26 -2
  21. data/test/liquid_test.rb +58 -0
  22. data/test/markaby_test.rb +58 -0
  23. data/test/markdown_test.rb +35 -0
  24. data/test/nokogiri_test.rb +69 -0
  25. data/test/radius_test.rb +59 -0
  26. data/test/rdoc_test.rb +34 -0
  27. data/test/request_test.rb +12 -0
  28. data/test/routing_test.rb +35 -1
  29. data/test/sass_test.rb +46 -16
  30. data/test/scss_test.rb +88 -0
  31. data/test/settings_test.rb +32 -0
  32. data/test/sinatra_test.rb +4 -0
  33. data/test/static_test.rb +64 -0
  34. data/test/templates_test.rb +55 -1
  35. data/test/textile_test.rb +34 -0
  36. data/test/views/ascii.haml +2 -0
  37. data/test/views/explicitly_nested.str +1 -0
  38. data/test/views/hello.coffee +1 -0
  39. data/test/views/hello.liquid +1 -0
  40. data/test/views/hello.mab +1 -0
  41. data/test/views/hello.md +1 -0
  42. data/test/views/hello.nokogiri +1 -0
  43. data/test/views/hello.radius +1 -0
  44. data/test/views/hello.rdoc +1 -0
  45. data/test/views/hello.sass +1 -1
  46. data/test/views/hello.scss +3 -0
  47. data/test/views/hello.str +1 -0
  48. data/test/views/hello.textile +1 -0
  49. data/test/views/layout2.liquid +2 -0
  50. data/test/views/layout2.mab +2 -0
  51. data/test/views/layout2.nokogiri +3 -0
  52. data/test/views/layout2.radius +2 -0
  53. data/test/views/layout2.str +2 -0
  54. data/test/views/nested.str +1 -0
  55. data/test/views/utf8.haml +2 -0
  56. metadata +240 -33
  57. data/lib/sinatra/tilt.rb +0 -746
data/CHANGES CHANGED
@@ -1,4 +1,111 @@
1
- = 1.0 / 2010-01-28 (prerelease)
1
+ = 1.1 / Not Yet Released
2
+
3
+ * Before and after filters now support pattern matching, including the
4
+ ability to use captures: "before('/user/:name') { |name| ... }". This
5
+ avoids manual path checking. No performance loss if patterns are avoided.
6
+ (Konstantin Haase)
7
+
8
+ * It is now possible to render SCSS files with the `scss` method, which
9
+ behaves exactly like `sass` except for the different file extension and
10
+ assuming the SCSS syntax. (Pedro Menezes, Konstantin Haase)
11
+
12
+ * Added `liquid`, `markdown`, `nokogiri`, `textile`, `rdoc`, `radius`,
13
+ `markaby`, and `coffee` rendering methods for rendering Liquid, Markdown,
14
+ Nokogiri, Textile, RDoc, Radius, Markaby and CoffeeScript templates.
15
+ (Konstantin Haase)
16
+
17
+ * Now supports byte-range requests (the HTTP_RANGE header) for static files.
18
+ Multi-range requests are not supported, however. (Jens Alfke)
19
+
20
+ * You can now use #settings method from class and top level for convenience.
21
+ (Konstantin Haase)
22
+
23
+ * Setting multiple values now no longer relies on #to_hash and therefore
24
+ accepts any Enumerable as parameter. (Simon Rozet)
25
+
26
+ * Nested templates default the `layout` option to `false` rather than `true`.
27
+ This eases the use of partials. If you wanted to render one haml template
28
+ embedded in another, you had to call `haml :partial, {}, :layout => false`.
29
+ As you almost never want the partial to be wrapped in the standard layout
30
+ in this situation, you now only have to call `haml :partial`. Passing in
31
+ `layout` explicitly is still possible. (Konstantin Haase)
32
+
33
+ * If a the return value of one of the render functions is used as a response
34
+ body and the content type has not been set explicitly, Sinatra chooses a
35
+ content type corresponding to the rendering engine rather than just using
36
+ "text/html". (Konstantin Haase)
37
+
38
+ * README is now available in French (Mickael Riga), German (Bernhard Essl,
39
+ Konstantin Haase, burningTyger), Hungarian (Janos Hardi) and Spanish
40
+ (Gabriel Andretta). The extremely outdated Japanese README has been updated
41
+ (Kouhei Yanagita).
42
+
43
+ * It is now possible to access Sinatra's template_cache from the outside.
44
+ (Nick Sutterer)
45
+
46
+ * The `last_modified` method now also accepts DateTime instances and makes
47
+ sure the header will always be set to a string. (Konstantin Haase)
48
+
49
+ * 599 now is a legal status code. (Steve Shreeve)
50
+
51
+ * This release is compatible with Ruby 1.9.2. Sinatra was trying to read
52
+ none existent files Ruby added to the call stack. (Shota Fukumori,
53
+ Konstantin Haase)
54
+
55
+ * Prevents a memory leak on 1.8.6 is production mode. Note, however, that
56
+ this is due to a bug in 1.8.6 and request will have the additional overhead
57
+ of parsing templates again on that version. It is recommended to use at
58
+ least Ruby 1.8.7. (Konstantin Haase)
59
+
60
+ * Compares last modified date correctly. `last_modified` was halting only
61
+ when the 'If-Modified-Since' header date was equal to the time specified.
62
+ Now, it halts when is equal or later than the time specified (Gabriel
63
+ Andretta).
64
+
65
+ * Sinatra is now usable in combination with Rails 3. When mounting a Sinatra
66
+ application under a subpath in Rails 3, the PATH_INFO is not prefixed with
67
+ a slash and no routes did match. (José Valim)
68
+
69
+ * Better handling of encodings in 1.9, defaults params encoding to UTF-8 and
70
+ respects Encoding.default_internal and Encoding.default_external.
71
+ (Konstantin Haase)
72
+
73
+ * `show_exeptions` handling is now triggered after custom error handlers, if
74
+ it is set to `:after_handlers`, thus not disabling those handler in
75
+ development mode. (pangel, Konstantin Haase)
76
+
77
+ * Added ability to handle weighted HTTP_ACCEPT headers. (Davide D'Agostino)
78
+
79
+ * `send_file` now always respects the `:type` option if set. Previously it
80
+ was discarded if no matching mime type was found, which made it impossible
81
+ to directly pass a mime type. (Konstantin Haase)
82
+
83
+ * `redirect` always redirects to an absolute URI, even if a relative URI was
84
+ passed. Ensures compatibility with RFC 2616 section 14.30. (Jean-Philippe
85
+ Garcia Ballester, Anthony Williams)
86
+
87
+ * Broken examples for using Erubis, Haml and Test::Unit in README have been
88
+ fixed. (Nick Sutterer, Doug Ireton, Jason Stewart, Eric Marden)
89
+
90
+ * Sinatra now handles SIGTERM correctly. (Patrick Collison)
91
+
92
+ * Fixes an issue with inline templates in modular applications that manually
93
+ call `run!`. (Konstantin Haase)
94
+
95
+ * Spaces after inline template names are now ignored (Konstantin Haase)
96
+
97
+ * It's now possible to use Sinatra with different package management
98
+ systems defining a custom require. (Konstantin Haase)
99
+
100
+ * Lighthouse has been dropped in favor of GitHub issues.
101
+
102
+ * Tilt is now a dependency and therefore no longer ships bundled with
103
+ Sinatra. (Ryan Tomayko, Konstantin Haase)
104
+
105
+ * Sinatra now depends on Rack 1.1 or higher. Rack 1.0 is no longer supported.
106
+ (Konstantin Haase)
107
+
108
+ = 1.0 / 2010-03-23
2
109
 
3
110
  * It's now possible to register blocks to run after each request using
4
111
  after filters. After filters run at the end of each request, after
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007, 2008, 2009 Blake Mizerany
1
+ Copyright (c) 2007, 2008, 2009, 2010 Blake Mizerany
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
@@ -0,0 +1,1024 @@
1
+ = Sinatra
2
+ <i>Wichtig: Dieses Dokument ist eine Übersetzung aus dem Englischen und unter Umständen nicht auf dem aktuellsten Stand.</i>
3
+
4
+ Sinatra ist eine DSL, die das schnelle Erstellen von Webanwendungen in Ruby
5
+ mit minimalen Aufwand ermöglicht:
6
+
7
+ # myapp.rb
8
+ require 'sinatra'
9
+ get '/' do
10
+ 'Hallo Welt!'
11
+ end
12
+
13
+ Einfach via rubygems installieren und starten:
14
+
15
+ gem install sinatra
16
+ ruby -rubygems myapp.rb
17
+
18
+ Die Seite kann nun unter http://localhost:4567 betrachtet werden.
19
+
20
+ == Routen
21
+
22
+ In Sinatra wird eine Route durch eine HTTP-Methode und ein URL-Muster definiert. Jeder dieser Routen wird ein
23
+ Ruby-Block zugeordnet.
24
+
25
+ get '/' do
26
+ .. zeige etwas ..
27
+ end
28
+
29
+ post '/' do
30
+ .. erstelle etwas ..
31
+ end
32
+
33
+ put '/' do
34
+ .. update etwas ..
35
+ end
36
+
37
+ delete '/' do
38
+ .. entferne etwas ..
39
+ end
40
+
41
+ Die Routen werden in der Reihenfolge durchlaufen, in der sie definiert wurden.
42
+ Das erste Routemuster, das mit dem Request übereinstimmt, wird ausgeführt.
43
+
44
+ Die Muster der Routen können benannte Parameter beinhalten, die über den
45
+ <tt>params</tt>-Hash zugänglich gemacht werden:
46
+
47
+ get '/hallo/:name' do
48
+ # passt auf "GET /hallo/foo" und "GET /hallo/bar"
49
+ # params[:name] ist 'foo' oder 'bar'
50
+ "Hallo #{params[:name]}!"
51
+ end
52
+
53
+ Man kann auf diese auch mit Blockparametern zugreifen:
54
+
55
+ get '/hallo/:name' do |n|
56
+ "Hallo #{n}!"
57
+ end
58
+
59
+ Routenmuster können auch mit Splat- oder Wildcardparametern über das
60
+ <tt>params[:splat]</tt> Array angesprochen werden.
61
+
62
+ get '/sag/*/zu/*' do
63
+ # passt auf /sag/hallo/zu/welt
64
+ params[:splat] # => ["hallo", "welt"]
65
+ end
66
+
67
+ get '/download/*.*' do
68
+ # passt auf /download/pfad/zu/datei.xml
69
+ params[:splat] # => ["pfad/zu/datei", "xml"]
70
+ end
71
+
72
+ Routen mit regulären Ausdrücken sind auch möglich:
73
+
74
+ get %r{/hallo/([\w]+)} do
75
+ "Hallo, #{params[:captures].first}!"
76
+ end
77
+
78
+ Und auch hier kann man Blockparameter nutzen:
79
+
80
+ get %r{/hallo/([\w]+)} do |c|
81
+ "Hallo, #{c}!"
82
+ end
83
+
84
+ === Bedingungen
85
+
86
+ An Routen können eine Vielzahl von Bedingungen angehängt werden, die erfüllt
87
+ sein müssen, damit der Block ausgeführt wird. Möglich wäre etwa eine
88
+ Einschränkung des User Agents:
89
+
90
+ get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do
91
+ "Du verwendest Songbird Version #{params[:agent][0]}"
92
+ end
93
+
94
+ get '/foo' do
95
+ # passt auf andere Browser
96
+ end
97
+
98
+ Andere mitgelieferte Bedingungen sind +host_name+ und +provides+:
99
+
100
+ get '/', :host_name => /^admin\./ do
101
+ "Adminbereich, Zugriff verweigert!"
102
+ end
103
+
104
+ get '/', :provides => 'html' do
105
+ haml :index
106
+ end
107
+
108
+ get '/', :provides => ['rss', 'atom', 'xml'] do
109
+ builder :feed
110
+ end
111
+
112
+ Man kann auch relativ einfach eigene Bedingungen hinzufügen:
113
+
114
+ set(:probability) { |value| condition { rand <= value } }
115
+
116
+ get '/auto_gewinnen', :probability => 0.1 do
117
+ "Du hast gewonnen!"
118
+ end
119
+
120
+ get '/auto_gewinnen' do
121
+ "Tut mir leid, verloren."
122
+ end
123
+
124
+ === Rückgabewerte
125
+
126
+ Durch den Rückgabewert eines Routenblocks wird mindestens der Response Body
127
+ festgelegt, der an den HTTP Client, bzw die nächste Rack Middleware
128
+ weitergegeben wird. Im Normalfall handelt es sich hierbei, wie
129
+ in den vorangehenden Beispielen zu sehen war, um einen String. Es werden allerdings auch andere
130
+ Werte akzeptiert.
131
+
132
+ Man kann jedes Objekt zurückgeben, bei dem es sich entweder um einenen validen
133
+ Rack-Rückgabewert, einen validen Rack-Body oder einen HTTP Status Code
134
+ handelt:
135
+
136
+ * Ein Array mit drei Elementen: <tt>[Status (Fixnum), Headers (Hash), Response Body (hört auf #each)]</tt>
137
+ * Ein Array mit zwei Elementen: <tt>[Status (Fixnum), Response Body (hört auf #each)]</tt>
138
+ * Ein Objekt, das auf <tt>#each</tt> hört und den an diese Methode übergebenen Block nur mit Strings als Übergabewerte aufruft.
139
+ * Ein Fixnum, das den Status Code festlegt.
140
+
141
+ Damit lässt sich relativ einfach Streaming implementieren:
142
+
143
+ class Stream
144
+ def each
145
+ 100.times { |i| yield "#{i}\n" }
146
+ end
147
+ end
148
+
149
+ get('/') { Stream.new }
150
+
151
+ == Statische Dateien
152
+
153
+ Statische Dateien werden aus dem <tt>./public</tt> Ordner ausgeliefert. Es ist
154
+ möglich einen anderen Ort zu definieren, indem man die <tt>:public</tt> Option
155
+ setzt:
156
+
157
+ set :public, File.dirname(__FILE__) + '/static'
158
+
159
+ Zu beachten ist, dass der Ordnername public nicht Teil der URL ist. Die Datei
160
+ <tt>./public/css/style.css</tt> ist unter
161
+ <tt>http://example.com/css/style.css</tt> zu finden.
162
+
163
+ == Views / Templates
164
+
165
+ Standardmäßig wird davon ausgegangen, dass sich Templates im
166
+ <tt>./views</tt> Ordner befinden. Man kann jedoch einen anderen Ordner
167
+ festlegen:
168
+
169
+ set :views, File.dirname(__FILE__) + '/templates'
170
+
171
+ Eine wichtige Sache, die man sich hierbei merken sollte, ist das man immer mit
172
+ Symbols auf Templates verweisen sollte, auch wenn sich ein Template in einem
173
+ Unterordner befindet (in diesen Fall <tt>:'subdir/template'</tt>).
174
+ Renderingmethoden rendern jeden String direkt.
175
+
176
+ === Haml-Templates
177
+
178
+ Das haml gem wird benötigt, um Haml-Templates rendern zu können:
179
+
180
+ ## haml muss eingebunden werden
181
+ require 'haml'
182
+
183
+ get '/' do
184
+ haml :index
185
+ end
186
+
187
+ Dieser Code rendert <tt>./views/index.haml</tt>.
188
+
189
+ {Hamls Optionen}[http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html#options]
190
+ können global durch die Sinatrakonfiguration gesetzt werden,
191
+ siehe {Optionen und Konfiguration}[http://www.sinatrarb.com/configuration.html],
192
+ und individuell überschrieben werden.
193
+
194
+ set :haml, :format => :html5 # Standard Haml-Format ist :xhtml
195
+
196
+ get '/' do
197
+ haml :index, :format => :html4 # überschrieben
198
+ end
199
+
200
+ === Erb-Templates
201
+
202
+ ## erb muss eingebunden werden
203
+ require 'erb'
204
+
205
+ get '/' do
206
+ erb :index
207
+ end
208
+
209
+ Dieser Code rendert <tt>./views/index.erb</tt>.
210
+
211
+ === Erubis
212
+
213
+ Das erubis gem wird benötigt, um Erubis-Templates rendern zu können:
214
+
215
+ ## erbubis muss eingebunden werden
216
+ require 'erubis'
217
+
218
+ get '/' do
219
+ erubis :index
220
+ end
221
+
222
+ Dieser Code rendert <tt>./views/index.erubis</tt>.
223
+
224
+ === Builder-Templates
225
+
226
+ Das buidler gem wird benötigt, um Builder-Templates rendern zu können:
227
+
228
+ ## builder muss eingebunden werden
229
+ require 'builder'
230
+
231
+ get '/' do
232
+ builder :index
233
+ end
234
+
235
+ Dieser Code rendert <tt>./views/index.builder</tt>.
236
+
237
+ === Nokogiri-Templates
238
+
239
+ Das nokogiri gem wird benötigt, um Nokogiri-Templates rendern zu können:
240
+
241
+ ## nokogiri muss eingebunden werden
242
+ require 'nokogiri'
243
+
244
+ get '/' do
245
+ nokogiri :index
246
+ end
247
+
248
+ Dieser Code rendert <tt>./views/index.nokogiri</tt>.
249
+
250
+ === Sass-Templates
251
+
252
+ Das haml gem wird benötigt, um SASS-Templates rendern zu können:
253
+
254
+ ## sass muss eingebunden werden
255
+ require 'sass'
256
+
257
+ get '/stylesheet.css' do
258
+ sass :stylesheet
259
+ end
260
+
261
+ Dieser Code rendert <tt>./views/stylesheet.sass</tt>.
262
+
263
+ {Sass Optionen}[http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options]
264
+ können global durch die Sinatra-Konfiguration gesetzt werden,
265
+ siehe {Optionen und Konfiguration}[http://www.sinatrarb.com/configuration.html],
266
+ und individuell überschrieben werden.
267
+
268
+ set :sass, :style => :compact # Standard Sass-Style ist :nested
269
+
270
+ get '/stylesheet.css' do
271
+ sass :stylesheet, :style => :expanded # überschrieben
272
+ end
273
+
274
+ === Scss-Templates
275
+
276
+ Das haml gem wird benötigt, um SCSS-Templates rendern zu können:
277
+
278
+ ## sass muss eingebunden werden
279
+ require 'sass'
280
+
281
+ get '/stylesheet.css' do
282
+ scss :stylesheet
283
+ end
284
+
285
+ Dieser Code rendert <tt>./views/stylesheet.scss</tt>.
286
+
287
+ {Scss Optionen}[http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options]
288
+ können global durch die Sinatra-Konfiguration gesetzt werden,
289
+ siehe {Optionen und Konfiguration}[http://www.sinatrarb.com/configuration.html],
290
+ und individuell überschrieben werden.
291
+
292
+ set :scss, :style => :compact # Standard Scss-Style ist :nested
293
+
294
+ get '/stylesheet.css' do
295
+ scss :stylesheet, :style => :expanded # überschrieben
296
+ end
297
+
298
+ === Less-Templates
299
+
300
+ Das less gem wird benötigt, um Less-Templates rendern zu können:
301
+
302
+ ## less muss eingebunden werden
303
+ require 'less'
304
+
305
+ get '/stylesheet.css' do
306
+ less :stylesheet
307
+ end
308
+
309
+ Dieser Code rendert <tt>./views/stylesheet.less</tt>.
310
+
311
+ === Liquid-Templates
312
+
313
+ Das liquid gem wird benötigt, um Liquid-Templates rendern zu können:
314
+
315
+ ## liquid muss eingebunden werden
316
+ require 'liquid'
317
+
318
+ get '/' do
319
+ liquid :index
320
+ end
321
+
322
+ Dieser Code rendert <tt>./views/index.liquid</tt>.
323
+
324
+ Da man aus Liquid-Templates heraus keine Methoden (abgesehen von +yield+)
325
+ aufrufen kann, will man nahezu in allen Fällen +locals+ übergeben:
326
+
327
+ liquid :index, :locals => { :key => 'value' }
328
+
329
+ === Markdown-Templates
330
+
331
+ Das rdiscount gem wird benötigt, um Markdown-Templates rendern zu können:
332
+
333
+ ## rdiscount muss eingebunden werden
334
+ require "rdiscount"
335
+
336
+ get '/' do
337
+ markdown :index
338
+ end
339
+
340
+ Dieser Code rendert <tt>./views/index.markdown</tt> (+md+ und +mkd+ sind
341
+ ebenfalls zulässige Dateiendungen).
342
+
343
+ Da es weder möglich ist Methoden aufzurufen, noch +locals+ zu übergeben, ist
344
+ es am sinnvollsten Markdown in Kombination mit einer anderen Template-Engine
345
+ zu nutzen:
346
+
347
+ erb :overview, :locals => { :text => markdown(:introduction) }
348
+
349
+ Es ist auch möglich die +markdown+ Methode aus anderen Templates heraus
350
+ aufzurufen:
351
+
352
+ %h1 Hallo von Haml!
353
+ %p= markdown(:greetings)
354
+
355
+ === Textile-Templates
356
+
357
+ Das RedCloth gem wird benötigt, um Textile-Templates rendern zu können:
358
+
359
+ ## redcloth muss eingebunden werden
360
+ require "redcloth"
361
+
362
+ get '/' do
363
+ textile :index
364
+ end
365
+
366
+ Dieser Code rendert <tt>./views/index.textile</tt>.
367
+
368
+ Da es weder möglich ist Methoden aufzurufen, noch +locals+ zu übergeben, ist
369
+ es am sinnvollsten Textile in Kombination mit einer anderen Template-Engine
370
+ zu nutzen:
371
+
372
+ erb :overview, :locals => { :text => textile(:introduction) }
373
+
374
+ Es ist auch möglich die +textile+ Methode aus anderen Templates heraus
375
+ aufzurufen:
376
+
377
+ %h1 Hallo von Haml!
378
+ %p= textile(:greetings)
379
+
380
+ === RDoc-Templates
381
+
382
+ Das rdoc gem wird benötigt, um RDoc-Templates rendern zu können:
383
+
384
+ ## RDoc muss eingebunden werden
385
+ require "rdoc"
386
+
387
+ get '/' do
388
+ rdoc :index
389
+ end
390
+
391
+ Dieser Code rendert <tt>./views/index.rdoc</tt>.
392
+
393
+ Da es weder möglich ist Methoden aufzurufen, noch +locals+ zu übergeben, ist
394
+ es am sinnvollsten RDoc in Kombination mit einer anderen Template-Engine
395
+ zu nutzen:
396
+
397
+ erb :overview, :locals => { :text => rdoc(:introduction) }
398
+
399
+ Es ist auch möglich die +rdoc+ Methode aus anderen Templates heraus
400
+ aufzurufen:
401
+
402
+ %h1 Hallo von Haml!
403
+ %p= rdoc(:greetings)
404
+
405
+ === Radius-Templates
406
+
407
+ Das radius gem wird benötigt, um Radius-Templates rendern zu können:
408
+
409
+ ## radius muss eingebunden werden
410
+ require 'radius'
411
+
412
+ get '/' do
413
+ radius :index
414
+ end
415
+
416
+ Dieser Code rendert <tt>./views/index.radius</tt>.
417
+
418
+ Da man aus Radius-Templates heraus keine Methoden (abgesehen von +yield+)
419
+ aufrufen kann, will man nahezu in allen Fällen +locals+ übergeben:
420
+
421
+ radius :index, :locals => { :key => 'value' }
422
+
423
+ === Markaby-Templates
424
+
425
+ Das markaby gem wird benötigt, um Markaby-Templates rendern zu können:
426
+
427
+ ## markaby muss eingebunden werden
428
+ require 'markaby'
429
+
430
+ get '/' do
431
+ markaby :index
432
+ end
433
+
434
+ Dieser Code rendert <tt>./views/index.mab</tt>.
435
+
436
+ === CoffeScript-Templates
437
+
438
+ Das coffee-script gem und das `coffee`-Programm werden benötigt, um CoffeScript-Templates rendern zu können:
439
+
440
+ ## coffee-script muss eingebunden werden
441
+ require 'coffee-script'
442
+
443
+ get '/application.js' do
444
+ coffee :application
445
+ end
446
+
447
+ Dieser Code rendert <tt>./views/application.coffee</tt>.
448
+
449
+ === Inline-Templates
450
+
451
+ get '/' do
452
+ haml '%div.title Hallo Welt'
453
+ end
454
+
455
+ Rendert den Inline-Template-String.
456
+
457
+ === Auf Variablen in Templates zugreifen
458
+
459
+ Templates werden im selben Kontext ausgeführt wie Routen. Instanzvariablen in
460
+ Routen sind auch direkt im Template verfügbar:
461
+
462
+ get '/:id' do
463
+ @foo = Foo.find(params[:id])
464
+ haml '%h1= @foo.name'
465
+ end
466
+
467
+ Oder durch einen expliziten Hash von lokalen Variablen:
468
+
469
+ get '/:id' do
470
+ foo = Foo.find(params[:id])
471
+ haml '%h1= foo.name', :locals => { :foo => foo }
472
+ end
473
+
474
+ Dies wird typischerweise bei Verwendung von Subtemplates (partials) in anderen
475
+ Templates eingesetzt.
476
+
477
+ === Inline-Templates
478
+
479
+ Templates können auch am Ende der Datei definiert werden:
480
+
481
+ require 'sinatra'
482
+
483
+ get '/' do
484
+ haml :index
485
+ end
486
+
487
+ __END__
488
+
489
+ @@ layout
490
+ %html
491
+ = yield
492
+
493
+ @@ index
494
+ %div.title Hallo Welt!!!!!
495
+
496
+ Anmerkung: Inline-Templates die in der Datei definiert sind, die <tt>require
497
+ 'sinatra'</tt> aufruft, werden automatisch geladen. Um andere Inline-Templates
498
+ in anderen Dateien aufzurufen, muss <tt>enable :inline_templates</tt> explizit
499
+ verwendet werden.
500
+
501
+ === Benannte Templates
502
+
503
+ Templates können auch mit der Top-Level <tt>template</tt>-Methode definiert
504
+ werden:
505
+
506
+ template :layout do
507
+ "%html\n =yield\n"
508
+ end
509
+
510
+ template :index do
511
+ '%div.title Hallo Welt!'
512
+ end
513
+
514
+ get '/' do
515
+ haml :index
516
+ end
517
+
518
+ Wenn ein Template mit dem Namen "layout" existiert, wird es bei jedem Aufruf
519
+ verwendet. Durch <tt>:layout => false</tt> kann das Ausführen verhindert werden.
520
+
521
+ get '/' do
522
+ haml :index, :layout => !request.xhr?
523
+ end
524
+
525
+ == Helfer
526
+
527
+ Durch die Top-Level <tt>helpers</tt>-Methode, werden sogenannte Helfer-Methoden
528
+ definiert, die in Routen und Templates verwendet werden können:
529
+
530
+ helpers do
531
+ def bar(name)
532
+ "#{name}bar"
533
+ end
534
+ end
535
+
536
+ get '/:name' do
537
+ bar(params[:name])
538
+ end
539
+
540
+ == Filter
541
+
542
+ Before-Filter werden immer vor jedem Request in dem selben Kontext wie danach
543
+ die Routen ausgeführt. So kann man etwa Request und Antwort ändern. Gesetzte
544
+ Instanzvariablen in Filtern können in Routen und Templates verwendet werden:
545
+
546
+ before do
547
+ @note = 'Hi!'
548
+ request.path_info = '/foo/bar/baz'
549
+ end
550
+
551
+ get '/foo/*' do
552
+ @note #=> 'Hi!'
553
+ params[:splat] #=> 'bar/baz'
554
+ end
555
+
556
+ After-Filter werden nach jedem Request im selben Kontext ausgeführt, und
557
+ können ebenfalls Request und Antwort ändern. In Before-Filtern gesetzte
558
+ Instanzvariablen können in After-Filterm verwendet werden:
559
+
560
+ after do
561
+ puts response.status
562
+ end
563
+
564
+ Filter können optional auch mit einem Pattern ausgestattet werden, welche auf den Request-Pfad passen müssen, damit der Filter ausgeführt wird:
565
+
566
+ before '/protected/*' do
567
+ authenticate!
568
+ end
569
+
570
+ after '/create/:slug' do |slug|
571
+ session[:last_slug] = slug
572
+ end
573
+
574
+ == Anhalten
575
+
576
+ Zum sofortigen stoppen eines Request in einem Filter oder einer Route:
577
+
578
+ halt
579
+
580
+ Der Status kann beim stoppen auch angegeben werden:
581
+
582
+ halt 410
583
+
584
+ Oder auch den Response-Body:
585
+
586
+ halt 'Hier steht der Body'
587
+
588
+ Oder beides:
589
+
590
+ halt 401, 'verschwinde!'
591
+
592
+ Sogar mit Headers:
593
+
594
+ halt 402, {'Content-Type' => 'text/plain'}, 'Rache'
595
+
596
+ == Weiterspringen
597
+
598
+ Eine Route kann mittels <tt>pass</tt> zu der nächsten passenden Route springen:
599
+
600
+ get '/raten/:wer' do
601
+ pass unless params[:wer] == 'Frank'
602
+ 'Du hast mich!'
603
+ end
604
+
605
+ get '/raten/*' do
606
+ 'Du hast mich verfehlt!'
607
+ end
608
+
609
+ Der Block wird sofort verlassen und es wird nach der nächsten treffenden Route
610
+ gesucht. Ein 404 Fehler wird zurückgegeben, wenn kein treffendes Routen-Muster
611
+ gefunden wird.
612
+
613
+ == Das Request-Objekt
614
+
615
+ Auf das `request`-Objeket der eigehenden Anfrage kann man vom Anfragescope aus zugreifen:
616
+
617
+ # App läuft unter http://example.com/example
618
+ get '/foo' do
619
+ request.body # Request Body des Clients (siehe unten)
620
+ request.scheme # "http"
621
+ request.script_name # "/example"
622
+ request.path_info # "/foo"
623
+ request.port # 80
624
+ request.request_method # "GET"
625
+ request.query_string # ""
626
+ request.content_length # Länge von request.body
627
+ request.media_type # Media-Type von request.body
628
+ request.host # "example.com"
629
+ request.get? # true (ähnliche Methoden für andere Verben)
630
+ request.form_data? # false
631
+ request["SOME_HEADER"] # Wert des SOME_HEADER-headers
632
+ request.referer # der Referrer des Clients oder '/'
633
+ request.user_agent # User Agent (genutzt von :agent-Bedingung)
634
+ request.cookies # Hash der Cookies
635
+ request.xhr? # Ist dies eine Ajax-Anfrage?
636
+ request.url # "http://example.com/example/foo"
637
+ request.path # "/example/foo"
638
+ request.ip # Client IP-Addresse
639
+ request.secure? # false
640
+ requuest.env # env-Hash den Rack durchreicht
641
+ end
642
+
643
+ Manche Optionen, wie etwa <tt>script_name</tt> oder <tt>path_info</tt> sind
644
+ auch schreibbar:
645
+
646
+ before { request.path_info = "/" }
647
+
648
+ get "/" do
649
+ "Alle Anfragen kommen hier an!"
650
+ end
651
+
652
+ Der <tt>request.body</tt> ist einn IO- oder StringIO-Objekt:
653
+
654
+ post "/api" do
655
+ request.body.rewind # falls schon jemand davon gelesen hat
656
+ daten = JSON.parse request.body.read
657
+ "Hallo #{daten['name']}!"
658
+ end
659
+
660
+ == Konfiguration
661
+
662
+ Wird einmal beim Starten in jedweder Umgebung ausgeführt:
663
+
664
+ configure do
665
+ ...
666
+ end
667
+
668
+ Läuft nur, wenn die Umgebung (RACK_ENV Umgebungsvariable) auf
669
+ <tt>:production</tt> gesetzt ist:
670
+
671
+ configure :production do
672
+ ...
673
+ end
674
+
675
+ Läuft nur, wenn die Umgebung auf <tt>:production</tt> oder auf <tt>:test</tt>
676
+ gesetzt ist:
677
+
678
+ configure :production, :test do
679
+ ...
680
+ end
681
+
682
+ == Fehlerbehandlung
683
+
684
+ Error Handler laufen im selben Kontext wie Routen und Filter, was bedeutet,
685
+ dass alle Goodies wie <tt>haml</tt>, <tt>erb</tt>, <tt>halt</tt>, etc.
686
+ verwendet werden können.
687
+
688
+ === Nicht gefunden
689
+
690
+ Wenn eine <tt>Sinatra::NotFound</tt> Exception geworfen wird oder der Statuscode 404 ist, wird der <tt>not_found</tt> Handler ausgeführt:
691
+
692
+ not_found do
693
+ 'Seite kann nirgendwo gefunden werden.'
694
+ end
695
+
696
+ === Fehler
697
+
698
+ Der +error+ Handler wird immer ausgeführt, wenn eine Exception in einem Routenblock
699
+ oder in einen Filter geworfen wurde. Die Exception kann über die
700
+ <tt>sinatra.error</tt> Rack-Variable angesprochen werden:
701
+
702
+ error do
703
+ 'Entschuldige es gab einen hässlichen Fehler - ' + env['sinatra.error'].name
704
+ end
705
+
706
+ Benutzerdefinierte Fehler:
707
+
708
+ error MeinFehler do
709
+ 'Was passiert ist...' + request.env['sinatra.error'].message
710
+ end
711
+
712
+ Dann, wenn das passiert:
713
+
714
+ get '/' do
715
+ raise MeinFehler, 'etwas schlechtes'
716
+ end
717
+
718
+ Bekommt man dieses:
719
+
720
+ Was passiert ist... etwas schlechtes
721
+
722
+ Alternativ kann ein Error Handler auch für Statuscode definiert werden:
723
+
724
+ error 403 do
725
+ 'Zugriff verboten'
726
+ end
727
+
728
+ get '/geheim' do
729
+ 403
730
+ end
731
+
732
+ Oder ein Statuscode-Bereich:
733
+
734
+ error 400..510 do
735
+ 'Boom'
736
+ end
737
+
738
+ Sinatra setzt verschiedene <tt>not_found</tt> und <tt>error</tt>
739
+ Handler in der Development Umgebung.
740
+
741
+ == Mime-Types
742
+
743
+ Wenn <tt>send_file</tt> oder statische Dateien verwendet werden, kann es
744
+ vorkommen, dass Sinatra den Mime-Typ nicht kennt. Registriert wird dieser mit
745
+ +mime_type+ per Dateiendung:
746
+
747
+ mime_type :foo, 'text/foo'
748
+
749
+ Es kann aber auch der +content_type+ Helfer verwendet werden:
750
+
751
+ content_type :foo
752
+
753
+ == Rack Middleware
754
+
755
+ Sinatra baut auf Rack[http://rack.rubyforge.org/], einem minimalen Standardinterface für Ruby Webframeworks. Eines der interessantesten
756
+ Features für Entwickler ist der Support von Middleware, die
757
+ zwischen den Server und die Anwendung geschaltet wird und so HTTP
758
+ Request und/oder Antwort überwachen und/oder manipulieren kann.
759
+
760
+ Sinatra macht das erstellen von Middleware-Verkettungen mit der Top-Level
761
+ Methode +use+ zu einem Kinderspiel:
762
+
763
+ require 'sinatra'
764
+ require 'meine_middleware'
765
+
766
+ use Rack::Lint
767
+ use MeineMiddleware
768
+
769
+ get '/hallo' do
770
+ 'Hallo Welt'
771
+ end
772
+
773
+ Die Semantik von +use+ entspricht der gleichnamigen Methode der
774
+ Rack::Builder[http://rack.rubyforge.org/doc/classes/Rack/Builder.html] DSL
775
+ (meist verwendet in Rackup-Dateien). Ein Beispiel dafür ist, dass die
776
+ +use+-Methode mehrere/verschiedene Argumente und auch Blöcke entgegen nimmt:
777
+
778
+ use Rack::Auth::Basic do |username, password|
779
+ username == 'admin' && password == 'geheim'
780
+ end
781
+
782
+ Rack bietet eine Vielzahl von standard Middleware für Logging, Debugging,
783
+ URL-Routing, Authentifizierung und Session-Verarbeitung.
784
+ Sinatra verwendet viele von diesen Komponenten automatisch, abhängig von der
785
+ Konfiguration. So muss man häufig +use+ nicht explizit verwenden.
786
+
787
+ == Testen
788
+
789
+ Sinatra Tests können mit jedem auf Rack aufbauendem Test Framework geschrieben
790
+ werden. {Rack::Test}[http://gitrdoc.com/brynary/rack-test] wird empfohlen:
791
+
792
+ require 'my_sinatra_app'
793
+ require 'test/unit'
794
+ require 'rack/test'
795
+
796
+ class MyAppTest < Test::Unit::TestCase
797
+ include Rack::Test::Methods
798
+
799
+ def app
800
+ Sinatra::Application
801
+ end
802
+
803
+ def test_my_default
804
+ get '/'
805
+ assert_equal 'Hallo Welt!', last_response.body
806
+ end
807
+
808
+ def test_with_params
809
+ get '/meet', :name => 'Frank'
810
+ assert_equal 'Hallo Frank!', last_response.body
811
+ end
812
+
813
+ def test_with_rack_env
814
+ get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
815
+ assert_equal "Du verwendest Songbird!", last_response.body
816
+ end
817
+ end
818
+
819
+ Anmerkung: Das eingebaute Sinatra::Test Modul und die Sinatra::TestHarness
820
+ Klasse werden seit Version 0.9.2 nicht mehr unterstützt.
821
+
822
+ == Sinatra::Base - Middleware, Bibliotheken, und modulare Anwendungen
823
+
824
+ Das Definitieren einer Top-Level Anwendung funktioniert gut für
825
+ Microanwendungen, hat aber Nachteile, wenn man wiederverwendbare Komponenten
826
+ wie Middleware, Rails Metal, einfache Bibliotheken mit Server Komponenten
827
+ oder auch Sinatra Erweiterungen bauen will.
828
+ Die Top-Level DSL belastet den Objekt-Namespace und setzt einen Microanwendungsstil voraus (eine einzelne Anwendungsdatei, ./public und ./views
829
+ Ordner, Logging, Exception-Detail-Seite, usw.). Genau hier kommt Sinatra::Base
830
+ ins Spiel:
831
+
832
+ require 'sinatra/base'
833
+
834
+ class MyApp < Sinatra::Base
835
+ set :sessions, true
836
+ set :foo, 'bar'
837
+
838
+ get '/' do
839
+ 'Hallo Welt!'
840
+ end
841
+ end
842
+
843
+ Die MyApp-Klasse ist eine unabhängige Rack-Komponente, die als Middleware,
844
+ Endpunkt oder via Rails Metal verwendet werden kann. Verwendet wird sie durch
845
+ +use+ oder +run+ von einer Rackup +config.ru+ Datei oder als Serverkomponente
846
+ einer Bibliothek:
847
+
848
+ MyApp.run! :host => 'localhost', :port => 9090
849
+
850
+ Die Methoden der Sinatra::Base-Subklasse sind genau die selben wie die
851
+ der Top-Level DSL. Die meisten Top-Level Anwendungen können mit nur zwei Veränderungen zu Sinatra::Base-Komponenten konvertiert werden:
852
+
853
+ * Die Datei sollte <tt>require 'sinatra/base'</tt> anstelle von
854
+ <tt>require 'sinatra/base'</tt> aufrufen, ansonsten werden alle von
855
+ Sinatras DSL Methoden in den Top-Level- Namespace importiert.
856
+ * Alle Routen, Error Handler, Filter und Optionen der Applikation müssen in
857
+ einer Subklasse von Sinatra::Base definiert werden.
858
+
859
+ <tt>Sinatra::Base</tt> ist ein unbeschriebense Blatt. Die meisten Optionen sind per default deaktiviert. Das betrifft auch den eingebauten Server. Siehe {Optionen und Konfiguration}[http://sinatra.github.com/configuration.html] für Details über möglichen Optionen.
860
+
861
+ === Sinatra als Middleware nutzen
862
+
863
+ Es ist nicht nur möglich andere Rack-Middleware mit Sinatra zu nutzen, man
864
+ kann außerdem jede Sinatra-Anwendung selbst als Middlware vor jeden beliebigen
865
+ Rack-Endpunkt hängen. Bei diesem Endpunkt muss es sich nicht um eine andere
866
+ Sinatra-Anwendung handen, es kann jede andere Rack-Anwendung sein
867
+ (Rails/Ramaze/Camping/...).
868
+
869
+ require 'sinatra/base'
870
+
871
+ class LoginScreen < Sinatra::Base
872
+ enable :session
873
+
874
+ get('/login') { haml :login }
875
+
876
+ post('/login') do
877
+ if params[:name] = 'admin' and params[:password] = 'admin'
878
+ session['user_name'] = params[:name]
879
+ else
880
+ redirect '/login'
881
+ end
882
+ end
883
+ end
884
+
885
+ class MyApp < Sinatra::Base
886
+ # Middleware wird vor Filtern ausgeführt
887
+ use LoginScreen
888
+
889
+ before do
890
+ unless session['user_name']
891
+ halt "Zugriff verweigert, bitte <a href='/login'>einloggen</a>."
892
+ end
893
+ end
894
+
895
+ get('/') { "Hallo #{session['user_name']}." }
896
+ end
897
+
898
+ == Scopes und Bindung
899
+
900
+ In welchem Scope man sich gerade befinded legt fest, welche Methoden und
901
+ Variablen zur Verfügung stehen.
902
+
903
+ === Anwendungs- oder Klassenscope
904
+
905
+ Jede Sinatra-Anwendung entspricht einer Sinatra::Base-Subklasse. Falls man die Top-Level-DSL verwendet (<tt>require 'sinatra'</tt>), so handelt es sich hierbei um Sinatra::Application, andernfalls is es jene Subklasse, die man explizit angelegt hat. Auf Klassenebene stehen Methoden wie `get` oder `before` zur Verfügung, man hat aber keinen Zugriff auf das `request`-Object oder die `session`, da nur eine einzige Klasse für alle eingehenden Anfragen genutzt wird.
906
+
907
+ Optionen die via `set` gesetzt werden, sind Methoden auf Klassenebene:
908
+
909
+ class MyApp << Sinatra::Base
910
+ # Hey, ich bin im Anwendungsscope!
911
+ set :foo, 42
912
+ foo # => 42
913
+
914
+ get '/foo' do
915
+ # Hey, ich bin nicht mehr im Anwendungsscope!
916
+ end
917
+ end
918
+
919
+ Im Anwendungsscope befindet man sich:
920
+
921
+ * In der Anwenungsklasse.
922
+ * In Methoden die von Erweiterungen definiert werden.
923
+ * Im Block, der an `helpers` übergeben wird.
924
+ * In Procs und Blöcken die an `set` übergeben werden.
925
+
926
+ Man kann auf das Scope-Object (die Klasse) wie folgt zugreifen:
927
+
928
+ * Über das Objekt, dass an den `configure`-Block übergeben wird (<tt>configure
929
+ { |c| ... }</tt>).
930
+ * `settings` aus den anderen Scopes heraus.
931
+
932
+ === Anfrage- oder Instanzscope
933
+
934
+ Für jede eingehende Anfrage wird eine neue Instanz der Anwendungsklasse erstellt und alle Handlers werden in diesem Scope ausgeführt. Aus diesem Scope heraus kann man auf `request` oder `session` zugreifen und Methoden wie `erb` oder `haml` aufrufen. Man kann mit der `settings` Methode außerdem auf den Anwengungsscope zugreifen.
935
+
936
+ class MyApp << Sinatra::Base
937
+ # Hey, ich bin im Anwendungsscope!
938
+ get '/neue_route/:name' do
939
+ # Anfragescope für '/neue_route/:name'
940
+ @value = 42
941
+
942
+ settings.get "/#{params[:name]}" do
943
+ # Anfragescope für "/#{params[:name]}"
944
+ @value # => nil (nicht die gleiche Anfrage)
945
+ end
946
+
947
+ "Route definiert!"
948
+ end
949
+ end
950
+
951
+ Im Anfragescope befindet man sich:
952
+
953
+ * In get/head/post/put/delete Blöcken
954
+ * In before/after Filtern
955
+ * In Helfermethoden
956
+ * In Templates
957
+
958
+ === Delegation-Scope
959
+
960
+ Vom Delegation-Scope aus werden Methoden einfach an den Klassenscope
961
+ weitergeleitet. Dieser verhält sich jedoch nicht 100%ig wie der Klassenscope,
962
+ da man nicht die Bindung der Klasse besitzt: Nur Methoden, die explizit als
963
+ deligierbar markiert wurden stehen hier zur Verfügung und man kann nicht auf
964
+ die Variablen des Klassenscopes zugreifen (mit anderen Worten: man hat ein
965
+ anderes `self`). Man kann mit <tt>Sinatra::Delegator.delegate
966
+ :methoden_name</tt> auch weitere Delegationen hinzufügen.
967
+
968
+ Im Delegation-Scop befindet man sich:
969
+
970
+ * Im Top-Level, wenn man <tt>require 'sinatra'</tt> aufgerufen hat.
971
+ * In einem Objekt, dass mit dem `Sinatra::Delegator` mixin erweitert wurde.
972
+
973
+ Schau am besten im Code nach: Hier ist {Sinatra::Delegator mixin}[http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064] definiert und wird in den {globalen Namespace eingebunden}[http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb#L25].
974
+
975
+ == Kommandozeile
976
+
977
+ Sinatra Anwendungen können direkt von der Kommandozeile aus gestartet werden:
978
+
979
+ ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-h HOST] [-s HANDLER]
980
+
981
+ Die Optionen sind:
982
+
983
+ -h # Hilfe
984
+ -p # Port setzen (Standard ist 4567)
985
+ -h # Host setzen (Standard ist 0.0.0.0)
986
+ -e # Umgebung setzen (Standard ist development)
987
+ -s # Rack Server/Handler setzen (Standard ist thin)
988
+ -x # Mutex lock einschalten (Standard ist off)
989
+
990
+ == Der neueste Stand (The Bleeding Edge)
991
+
992
+ Um auf den neuesten Stand von Sinatras Code zu sein, kann eine lokale Kopie
993
+ angelegt werden. Gestartet wird in der Anwendung mit dem <tt>sinatra/lib</tt>
994
+ Ordner im <tt>LOAD_PATH</tt>:
995
+
996
+ cd myapp
997
+ git clone git://github.com/sinatra/sinatra.git
998
+ ruby -Isinatra/lib myapp.rb
999
+
1000
+ Alternativ kann der <tt>sinatra/lib</tt> Ordner zum <tt>LOAD_PATH</tt> in
1001
+ der Anwendung hinzugefügt werden:
1002
+
1003
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib'
1004
+ require 'rubygems'
1005
+ require 'sinatra'
1006
+
1007
+ get '/ueber' do
1008
+ "Ich laufe auf Version " + Sinatra::VERSION
1009
+ end
1010
+
1011
+ Um Sinatra Code von Zeit zu Zeit zu aktualisieren:
1012
+
1013
+ cd myproject/sinatra
1014
+ git pull
1015
+
1016
+ == Mehr
1017
+
1018
+ * {Projekt Website}[http://sinatra.github.com/] - Ergänzende Dokumentation,
1019
+ News und Links zu anderen Ressourcen.
1020
+ * {Hilfe beisteuern}[http://sinatra.github.com/contributing.html] - Einen Fehler gefunden? Brauchst du Hilfe? Hast du einen Patch?
1021
+ * {Issue Tracker}[http://github.com/sinatra/sinatra/issues]
1022
+ * {Twitter}[http://twitter.com/sinatra]
1023
+ * {Mailingliste}[http://groups.google.com/group/sinatrarb]
1024
+ * {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] auf http://freenode.net