tynn 2.0.0.alpha → 2.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3428a8bf437b03bf9e9dc2d2568143010f5a8be7
4
- data.tar.gz: 9a36c6091a73ced16b4b5a5e9f8cec60fa6fd39e
3
+ metadata.gz: 2017e12fdca96b52fb5c2f954307b33e208c2ecb
4
+ data.tar.gz: 02cc565109da5f90593c370f818f213969c63c86
5
5
  SHA512:
6
- metadata.gz: 25196be28e4b69d4d0ea609bcdfa7fa08276716c6e94753347ac17c8dfad6d6c9f9b390559b79ca9c9f67fbec78dec2379a481c21497460768502649bc95c63e
7
- data.tar.gz: a5bdf9e80a0d95d9d172264a00612dc0c86666a1c25c761598363e100279f6c32d09d20235d3d1a0216a0ed29f2890a80da263e431c9e30f125aaa3081a418fc
6
+ metadata.gz: afcf9ebeb3457d3a7a9bd06878aa98cd9c88f8bf47dc1ecfeea008e4ad7571f3a9d1b0abff93b8ea68073edce4d2e0d59de72ce479c89e2fff458b4d5d954cfd
7
+ data.tar.gz: 4da2694ca6ec7484471c7c3ae51ca4406be40e0663ba6dce147adc2cc38d5d0abef0b88a214bc81cc8efe7334b2adcde456f6fe5e93ff66bbdc4921c094b8338
data/README.md CHANGED
@@ -3,7 +3,6 @@
3
3
  [![Build Status](https://travis-ci.org/frodsan/tynn.svg?branch=master)](https://travis-ci.org/frodsan/tynn)
4
4
  [![Dependency Status](https://gemnasium.com/badges/github.com/frodsan/tynn.svg)](https://gemnasium.com/github.com/frodsan/tynn)
5
5
  [![Code Climate](https://codeclimate.com/github/frodsan/tynn/badges/gpa.svg)](https://codeclimate.com/github/frodsan/tynn)
6
- [![Join the chat at https://gitter.im/frodsan/tynn](https://badges.gitter.im/frodsan/tynn.svg)](https://gitter.im/frodsan/tynn?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7
6
 
8
7
  A thin library for web development in Ruby.
9
8
 
@@ -14,9 +13,20 @@ A thin library for web development in Ruby.
14
13
  * [Under the Hood](#under-the-hood)
15
14
  * [Tynn on Rack](#tynn-on-rack)
16
15
  * [Routing Basics](#routing-basics)
16
+ * [HTTP Verbs](#http-verbs)
17
+ * [Matching path segments](#matching-path-segments)
18
+ * [Capturing path segments](#capturing-path-segments)
19
+ * [Matching more than paths](#matching-more-than-paths)
20
+ * [Routing rules](#routing-rules)
21
+ * [Composing Applications](#composing-applications)
17
22
  * [Managing Request and Response](#managing-request-and-response)
23
+ * [Accessing Request Parameters](#accessing-request-parameters)
24
+ * [Setting a Status Code](#setting-a-status-code)
25
+ * [Writting to the Response Body](#writting-to-the-response-body)
26
+ * [Setting HTTP Headers](#setting-http-headers)
18
27
  * [Redirecting a Request](#redirecting-a-request)
19
28
  * [Halting a Request](#halting-a-request)
29
+ * [Cookies](#cookies)
20
30
  * [Extending Tynn](#extending-tynn)
21
31
  * [Middleware](#middleware)
22
32
  * [Plugins](#plugins)
@@ -24,8 +34,13 @@ A thin library for web development in Ruby.
24
34
  * [Default Plugins](#default-plugins)
25
35
  * [Environments](#environments)
26
36
  * [Method Override](#method-override)
37
+ * [JSON](#json)
38
+ * [Rendering Templates](#rendering-templates)
27
39
  * [Static Files](#static-files)
40
+ * [Sessions](#sessions)
28
41
  * [Security](#security)
42
+ * [Secure Headers](#secure-headers)
43
+ * [HTTPS](#https)
29
44
  * [Testing](#testing)
30
45
  * [API Reference](http://api.tynn.xyz/2.0.0)
31
46
  * [Changelog](#changelog)
@@ -46,7 +61,7 @@ Tynn is not a framework. There is no built-in support for connecting to database
46
61
 
47
62
  Like most web libraries in Ruby, Tynn is built on top of [Rack], a Ruby webserver interface. Because of this, it has the benefits of using existing libraries and a variety of web servers for free.
48
63
 
49
- Tynn takes design cues from other web libraries like [Rails], [Sinatra], and [Cuba]. Under the hood, it uses [Syro], a fast router for web applications.
64
+ Tynn takes design cues from other web libraries like [Rails], [Sinatra], and [Cuba].
50
65
 
51
66
  ### Assumptions
52
67
 
@@ -91,7 +106,7 @@ require "tynn"
91
106
 
92
107
  Tynn.define do
93
108
  on root? do
94
- get do
109
+ on get do
95
110
  res.write("Hello World!")
96
111
  end
97
112
  end
@@ -133,7 +148,7 @@ That's a little taste of what we call routing matchers in Tynn. The purpose of t
133
148
  One line below there is another type of matcher:
134
149
 
135
150
  ```ruby
136
- get do
151
+ on get do
137
152
  res.write("Hello World!")
138
153
  end
139
154
  ```
@@ -142,7 +157,7 @@ Tynn also includes methods for matching all the HTTP verbs (GET, POST, etc.). By
142
157
 
143
158
  ```ruby
144
159
  Tynn.define do
145
- get do
160
+ on get do
146
161
  res.write("Hello World!")
147
162
  end
148
163
  end
@@ -175,10 +190,72 @@ Most popular web servers, like [Puma], [Unicorn] or [Thin], have built-in suppor
175
190
 
176
191
  This section covers the routing features of Tynn, such as route definitions, composing applications, and so on.
177
192
 
193
+ ### HTTP Verbs
194
+
195
+ TODO.
196
+
197
+ ### Matching path segments
198
+
199
+ TODO.
200
+
201
+ ### Capturing path segments
202
+
203
+ TODO.
204
+
205
+ ### Matching more than paths
206
+
207
+ TODO.
208
+
209
+ ### Routing rules
210
+
211
+ TODO.
212
+
213
+ ### Composing Applications
214
+
215
+ TODO.
216
+
178
217
  ## Managing Request and Response
179
218
 
180
219
  This section covers the many features Tynn provides to handle requests and responses.
181
220
 
221
+ ### Accessing Request Parameters
222
+
223
+ TODO.
224
+
225
+ ### Setting a Status Code
226
+
227
+ TODO.
228
+
229
+ ### Writting to the Response Body
230
+
231
+ TODO.
232
+
233
+ ### Setting HTTP Headers
234
+
235
+ To set HTTP headers in the response, use the `res.headers` hash.
236
+
237
+ ```ruby
238
+ Tynn.define do
239
+ on get do
240
+ res.headers["Content-Type"] = "application/json"
241
+
242
+ res.write(JSON.generate(status: "ok"))
243
+ end
244
+ end
245
+ ```
246
+
247
+ Default headers for the application can be set through the `:default_headers` setting.
248
+
249
+ ```ruby
250
+ Tynn.set(:default_headers, "Content-Type" => "application/json")
251
+
252
+ Tynn.define do
253
+ on get do
254
+ res.write(JSON.generate(status: "ok"))
255
+ end
256
+ end
257
+ ```
258
+
182
259
  ### Redirecting a Request
183
260
 
184
261
  To redirect a request to a different location, use `res.redirect`.
@@ -219,8 +296,14 @@ end
219
296
  # This won't be reached if current_user is nil
220
297
  ```
221
298
 
299
+ ### Cookies
300
+
301
+ TODO.
302
+
222
303
  ## Extending Tynn
223
304
 
305
+ TODO.
306
+
224
307
  ### Middleware
225
308
 
226
309
  Tynn runs on [Rack](https://github.com/rack/rack). Therefore it is possible
@@ -286,7 +369,7 @@ Here is the plugin in action:
286
369
  App.currency # => "$"
287
370
 
288
371
  App.define do
289
- get do
372
+ on get do
290
373
  res.write(to_currency(4567))
291
374
  end
292
375
  end
@@ -330,17 +413,7 @@ end
330
413
 
331
414
  ## Default Plugins
332
415
 
333
- Tynn ships with a set of default plugins:
334
-
335
- | Name | Description
336
- | --------------------- | -----------------------------------------------------------------
337
- | [Tynn::Environment] | Adds helper methods to get and check the current environment.
338
- | [Tynn::JSON] | Adds helper methods for json generation.
339
- | [Tynn::Render] | Adds support for rendering templates through different engines.
340
- | [Tynn::Session] | Adds simple cookie based session management.
341
- | [Tynn::Static] | Adds support for static files (javascript files, images, etc.).
342
-
343
- The following sections cover the default plugins and extensions shipped with Tynn.
416
+ Tynn ships with a set of default plugins. The following sections cover the default plugins and extensions shipped with Tynn.
344
417
 
345
418
  ### Environments
346
419
 
@@ -398,6 +471,33 @@ Tynn.configure(:production) do |app|
398
471
  end
399
472
  ```
400
473
 
474
+ ### JSON
475
+
476
+ Tynn ships with [Tynn::JSON] to serve JSON resources. It adds a helper method, called `json`, that generates a JSON document and writes it to the response body. This also automatically sets the content type header to `application/json`.
477
+
478
+ ```ruby
479
+ require "tynn"
480
+ require "tynn/json"
481
+
482
+ Tynn.define do
483
+ on "array" do
484
+ on get do
485
+ json(["foo", "bar", "baz"])
486
+ end
487
+ end
488
+
489
+ on "object" do
490
+ on get do
491
+ json(foo: "bar")
492
+ end
493
+ end
494
+ end
495
+ ```
496
+
497
+ ### Rendering Templates
498
+
499
+ TODO.
500
+
401
501
  ### Method Override
402
502
 
403
503
  HTML Forms only support GET and POST requests. To perform other actions such as PUT, PATCH or DELETE, use the [Rack::MethodOverride] middleware. Note that there is no need to add any new dependencies to the application as it's included in Rack already.
@@ -420,7 +520,7 @@ Now, this will trigger the `put` matcher in the application.
420
520
 
421
521
  ```ruby
422
522
  Posts.define do
423
- put do
523
+ on put do
424
524
  post.update(req.params["post"])
425
525
  end
426
526
  end
@@ -462,8 +562,45 @@ Tynn.plugin(
462
562
  )
463
563
  ```
464
564
 
565
+ ### Sessions
566
+
567
+ TODO.
568
+
465
569
  ## Security
466
570
 
571
+ Many of the default plugins include secure default options. Here are some plugins that can improve the security of your web application.
572
+
573
+ ### Secure Headers
574
+
575
+ Tynn ships with the [Tynn::SecureHeaders] plugin.
576
+
577
+ ```ruby
578
+ require "tynn"
579
+ require "tynn/secure_headers"
580
+
581
+ Tynn.plugin(Tynn::SecureHeaders)
582
+ ```
583
+
584
+ This plugins adds the following security related HTTP headers to each response.
585
+
586
+ - **X-Content-Type-Options:** Prevents IE and Chrome from [Content Type Sniffing][sniffing]. Defaults to `"nosniff"`.
587
+
588
+ - **X-Frame-Options:** Provides [Clickjacking] protection. Defaults to `"deny"`.
589
+
590
+ - **X-XSS-Protection:** Enables the XSS protection filter built into IE, Chrome and Safari. This filter is usually enabled by default, the use of this header is to re-enable it if it was turned off by the user. Defaults to `"1; mode=block"</tt`.
591
+
592
+ You can configure the default headers through the `:default_headers` setting.
593
+
594
+ ```ruby
595
+ Tynn.set(:default_headers, {
596
+ "X-Frame-Options" => "sameorigin"
597
+ })
598
+ ```
599
+
600
+ ### HTTTPS
601
+
602
+ TODO.
603
+
467
604
  ## Testing
468
605
 
469
606
  Tynn ships with [Tynn::Test], a simple helper class to simulate requests to your application.
@@ -473,16 +610,16 @@ require "tynn"
473
610
  require "tynn/test"
474
611
 
475
612
  Tynn.define do
476
- root do
613
+ on get do
477
614
  res.write("hei")
478
615
  end
479
616
  end
480
617
 
481
- app = Tynn::Test.new
482
- app.get("/")
618
+ ts = Tynn::Test.new
619
+ ts.get("/")
483
620
 
484
- 200 == app.res.status # => true
485
- "hei" == app.res.body # => true
621
+ 200 == ts.res.status # => true
622
+ "hei" == ts.res.body.join # => true
486
623
  ```
487
624
 
488
625
  [Tynn::Test] is test-framework agnostic. The following example uses [Minitest]:
@@ -493,15 +630,15 @@ require "tynn/test"
493
630
 
494
631
  class GuestsRouteTest < Minitest::Test
495
632
  def setup
496
- @app = Tynn::Test.new
633
+ @ts = Tynn::Test.new
497
634
  end
498
635
 
499
636
  def test_home
500
- @app.get("/")
637
+ @ts.get("/")
501
638
 
502
- assert_equal 200, @app.res.status
503
- assert_equal "Hello World!", @app.res.body
504
- assert_equal "text/html", @app.res["Content-Type"]
639
+ assert_equal 200, @ts.res.status
640
+ assert_equal "Hello World!", @ts.res.body.join
641
+ assert_equal "text/html", @ts.res.headers["Content-Type"]
505
642
  end
506
643
  end
507
644
  ```
@@ -546,6 +683,7 @@ Tynn is released under the [MIT License](http://www.opensource.org/licenses/MIT)
546
683
 
547
684
  [bundler]: http://bundler.io/
548
685
  [capybara]: https://github.com/jnicklas/capybara
686
+ [clickjacking]: https://www.owasp.org/index.php/Clickjacking
549
687
  [cuba]: http://cuba.is/
550
688
  [minitest]: https://github.com/seattlerb/minitest
551
689
  [puma]: http://puma.io/
@@ -554,11 +692,12 @@ Tynn is released under the [MIT License](http://www.opensource.org/licenses/MIT)
554
692
  [rack::methodoverride]: http://www.rubydoc.info/github/rack/rack/Rack/MethodOverride
555
693
  [rails]: http://rubyonrails.org/
556
694
  [sinatra]: http://www.sinatrarb.com/
557
- [syro]: http://soveran.github.io/syro/
695
+ [sniffing]: https://msdn.microsoft.com/library/gg622941(v=vs.85).aspx
558
696
  [thin]: http://code.macournoyer.com/thin/
559
697
  [tynn::environment]: http://api.tynn.xyz/2.0.0/Tynn/Environment.html
560
698
  [tynn::json]: http://api.tynn.xyz/2.0.0/Tynn/JSON.html
561
699
  [tynn::render]: http://api.tynn.xyz/2.0.0/Tynn/Render.html
700
+ [tynn::secureheaders]: http://api.tynn.xyz/2.0.0/Tynn/SecureHeaders.html
562
701
  [tynn::session]: http://api.tynn.xyz/2.0.0/Tynn/Session.html
563
702
  [tynn::static]: http://api.tynn.xyz/2.0.0/Tynn/Static.html
564
703
  [tynn::test]: http://api.tynn.xyz/2.0.0/Tynn/Test.html
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "tynn/base"
4
- require_relative "tynn/default_headers"
5
- require_relative "tynn/settings"
6
4
  require_relative "tynn/version"
7
5
 
8
6
  class Tynn
@@ -69,6 +67,4 @@ class Tynn
69
67
  end
70
68
 
71
69
  plugin(Tynn::Base)
72
- plugin(Tynn::Settings)
73
- plugin(Tynn::DefaultHeaders)
74
70
  end
@@ -1,8 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "syro"
3
+ # Copyright (c) 2016 Francesco Rodriguez
4
+ # Copyright (c) 2015-2016 Michel Martens (Portions of https://github.com/soveran/syro)
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+
24
+ require "rack"
25
+ require "seg"
26
+ require_relative "errors"
4
27
  require_relative "request"
5
28
  require_relative "response"
29
+ require_relative "utils"
6
30
 
7
31
  class Tynn
8
32
  module Base
@@ -13,19 +37,19 @@ class Tynn
13
37
  # end
14
38
  #
15
39
  # Users.define do
16
- # on(:id) do |id|
17
- # get do
40
+ # on :id do |id|
41
+ # on get do
18
42
  # res.write("GET /users/#{ id }")
19
43
  # end
20
44
  #
21
- # post do
45
+ # on post do
22
46
  # res.write("POST /users/#{ id }")
23
47
  # end
24
48
  # end
25
49
  # end
26
50
  #
27
51
  def define(&block)
28
- @__app = build_app(Syro.new(self, &block))
52
+ @__app = build_app(proc { |env| new(block).call(env) })
29
53
  end
30
54
 
31
55
  def build_app(app) # :nodoc:
@@ -44,8 +68,27 @@ class Tynn
44
68
  # Tynn.use(Rack::CommonLogger)
45
69
  # Tynn.use(Rack::ShowExceptions)
46
70
  #
71
+ # If applications handler is already set, it raises an error.
72
+ #
73
+ # Tynn.define {}
74
+ #
75
+ # Tynn.use(Rack::CommonLogger)
76
+ # # => Application middleware is frozen.
77
+ #
47
78
  def use(middleware, *args, &block)
48
- self.middleware.push(proc { |app| middleware.new(app, *args, &block) })
79
+ if self.middleware.frozen?
80
+ raise Tynn::Error, <<~MSG
81
+ Application middleware is frozen and cannot be changed.
82
+
83
+ Please, set the middleware before setting the application handler:
84
+
85
+ #{ self }.use(#{ middleware })
86
+
87
+ #{ self }.define { ... }
88
+ MSG
89
+ else
90
+ self.middleware.push(proc { |app| middleware.new(app, *args, &block) })
91
+ end
49
92
  end
50
93
 
51
94
  def middleware # :nodoc:
@@ -58,39 +101,340 @@ class Tynn
58
101
 
59
102
  def app # :nodoc:
60
103
  (defined?(@__app) && @__app) or
61
- raise("Application handler is missing. Try #{ self }.define {}")
104
+ raise(Tynn::Error, "Application handler is missing. Try #{ self }.define {}")
105
+ end
106
+
107
+ # Copies settings into the subclass. If a setting is not found,
108
+ # checks parent's settings.
109
+ def inherited(subclass) # :nodoc:
110
+ subclass.settings.replace(Tynn::Utils.deepclone_hash(settings))
111
+ subclass.settings.default_proc = proc { |h, k| h[k] = settings[k] }
112
+ end
113
+
114
+ # Returns a Hash with the application settings.
115
+ #
116
+ # Tynn.set(:environment, :development)
117
+ #
118
+ # Tynn.settings
119
+ # # => { :environment => :development }
120
+ #
121
+ def settings
122
+ @settings ||= {}
123
+ end
124
+
125
+ # Sets an <tt>option</tt> to the given </tt>value</tt>. If a setting
126
+ # with the <tt>option</tt> key exists and is a hash value, it merges
127
+ # the stored hash with <tt>value</tt>.
128
+ #
129
+ # Tynn.set(:environment, :staging)
130
+ #
131
+ # Tynn.settings[:environment]
132
+ # # => :staging
133
+ #
134
+ # Tynn.default_headers
135
+ # # => { "Content-Type" => "text/html" }
136
+ #
137
+ # Tynn.set(:default_headers, "X-Frame-Options" => "DENY")
138
+ #
139
+ # Tynn.default_headers
140
+ # # => { "Content-Type" => "text/html", "X-Frame-Options" => "DENY" }
141
+ #
142
+ def set(option, value)
143
+ v = settings[option]
144
+
145
+ if Hash === v
146
+ set!(option, v.merge(value))
147
+ else
148
+ set!(option, value)
149
+ end
150
+ end
151
+
152
+ # Sets an <tt>option</tt> to the given </tt>value</tt>.
153
+ #
154
+ # Tynn.set!(:environment, :staging)
155
+ #
156
+ # Tynn.settings[:environment]
157
+ # # => :staging
158
+ #
159
+ # Tynn.default_headers
160
+ # # => { "Content-Type" => "text/html" }
161
+ #
162
+ # Tynn.set!(:default_headers, "X-Frame-Options" => "DENY")
163
+ #
164
+ # Tynn.default_headers
165
+ # # => { "X-Frame-Options" => "DENY" }
166
+ #
167
+ def set!(option, value)
168
+ settings[option] = value
169
+ end
170
+
171
+ # Returns a Hash with the default headers.
172
+ #
173
+ # Tynn.set(:default_headers, {
174
+ # "Content-Type" => "application/json"
175
+ # })
176
+ #
177
+ # Tynn.default_headers["Content-Type"]
178
+ # # => "application/json"
179
+ #
180
+ def default_headers
181
+ settings.fetch(:default_headers, {})
62
182
  end
63
183
  end
64
184
 
65
- module InstanceMethods # :nodoc:
66
- include Syro::Deck::API
185
+ # Monkey-patches capture to return the path segment instead of storing it
186
+ # in a hash.
187
+ class Seg < ::Seg # :nodoc:
188
+ def capture
189
+ return nil if root?
190
+
191
+ len = (@path.index(SLASH, @pos) || @size) - @pos
192
+
193
+ segment = @path[@pos, len]
194
+
195
+ move(len)
196
+
197
+ segment
198
+ end
199
+ end
200
+
201
+ module InstanceMethods
202
+ def initialize(code) # :nodoc:
203
+ @__code = code
204
+ end
205
+
206
+ def call(env) # :nodoc:
207
+ @__env = env
208
+ @__req = Tynn::Request.new(env)
209
+ @__res = Tynn::Response.new(Hash[self.class.default_headers])
210
+ @__seg = Tynn::Base::Seg.new(env["PATH_INFO"])
211
+
212
+ catch(:halt) do
213
+ instance_eval(&@__code)
214
+
215
+ @__res.finish
216
+ end
217
+ end
67
218
 
68
- # Overrides Syro::Deck::API#on to support passing the receiver to
69
- # the capturing matchers.
219
+ # Returns the incoming request object. This object is an instance of
220
+ # Tynn::Request.
70
221
  #
71
- # on :name do |name|
222
+ # req.post?
223
+ # # => true
224
+ #
225
+ # req.params
226
+ # # => { "username" => "bob", "password" => "secret" }
227
+ #
228
+ # req.headers["Content-Type"]
229
+ # # => "application/x-www-form-urlencoded"
230
+ #
231
+ def req
232
+ @__req
233
+ end
234
+
235
+ # Returns the current response object. This object is an instance of
236
+ # Tynn::Response.
237
+ #
238
+ # res.status = 200
239
+ # res.headers["Content-Type"] = "text/html"
240
+ # res.write("<h1>Welcome back!</h1>")
241
+ #
242
+ def res
243
+ @__res
244
+ end
245
+
246
+ # Executes a given block if <tt>arg</tt> matches one of these conditions.
247
+ #
248
+ # If <tt>arg</tt> is a string, it matches a path segment.
249
+ #
250
+ # Tynn.define do
251
+ # # Matches if /foo
252
+ # on "foo" do
253
+ # # Matches if /foo/bar/baz
254
+ # on "bar/baz" do
255
+ # res.write("/foo/bar/baz")
256
+ # end
257
+ # end
72
258
  # end
73
259
  #
74
- # # instead of
260
+ # If <tt>arg</tt> is a symbol, it matches and capture a path segment.
75
261
  #
76
- # on :name do
77
- # name = inbox[:name]
262
+ # Tynn.define do
263
+ # # Matches if /users
264
+ # on "users" do
265
+ # # Captures id if matches /users/id
266
+ # on :id do |id|
267
+ # res.write("/users/#{ id }")
268
+ # end
269
+ # end
270
+ # end
271
+ #
272
+ # Finally, if <tt>arg</tt> is <tt>true</tt>, it executes the given block
273
+ # inconditionally.
274
+ #
275
+ # Tynn.define do
276
+ # on current_user.nil? do
277
+ # res.status = 401
278
+ # res.write("Unauthorized")
279
+ # end
78
280
  # end
79
281
  #
80
282
  def on(arg)
81
- default { yield((arg.is_a?(Symbol) ? inbox[arg] : nil)) } if match(arg)
283
+ (v = match(arg)) && yield(v)
284
+ end
285
+
286
+ def match(arg)
287
+ case arg
288
+ when String then @__seg.consume(arg)
289
+ when Symbol then @__seg.capture
290
+ when true then true
291
+ else false
292
+ end
293
+ end
294
+
295
+ private :match
296
+
297
+ # Immediately stops the request and returns <tt>response</tt>
298
+ # as per Rack's specification.
299
+ #
300
+ # halt([200, { "Content-Type" => "text/html" }, ["hello"]])
301
+ # halt(res.finish)
302
+ #
303
+ def halt(response)
304
+ throw(:halt, response)
305
+ end
306
+
307
+ # Runs a Tynn <tt>app</tt> and pass an optional hash of values to it.
308
+ #
309
+ # Tynn.define do
310
+ # on "admin" do
311
+ # run(Admin, subdomain: subdomain)
312
+ # end
313
+ # end
314
+ #
315
+ def run(app, inbox = nil)
316
+ path, script = @__env["PATH_INFO"], @__env["SCRIPT_NAME"]
317
+
318
+ @__env["PATH_INFO"] = @__seg.curr
319
+ @__env["SCRIPT_NAME"] = @__seg.prev
320
+ @__env["tynn.inbox"] = inbox
321
+
322
+ halt(app.call(@__env))
323
+ ensure
324
+ @__env["PATH_INFO"], @__env["SCRIPT_NAME"] = path, script
325
+ end
326
+
327
+ # Returns a hash with variables passed to the application.
328
+ #
329
+ # Tynn.define do
330
+ # on "api/v1" do
331
+ # run(API, version: 1)
332
+ # end
333
+ #
334
+ # on "api/v2" do
335
+ # run(API, version: 2)
336
+ # end
337
+ # end
338
+ #
339
+ # API.define do
340
+ # version = inbox[:version]
341
+ # end
342
+ #
343
+ def inbox
344
+ @__env["tynn.inbox"] || {}
345
+ end
346
+
347
+ # Returns <tt>true</tt> if the request method is <tt>GET</tt> and
348
+ # the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
349
+ #
350
+ # Tynn.define do
351
+ # on "users" do
352
+ # on get do
353
+ # res.write("GET /users")
354
+ # end
355
+ # end
356
+ # end
357
+ #
358
+ def get
359
+ root? && req.get?
82
360
  end
83
361
 
84
- # Overrides request class used by Syro's router.
85
- # See Tynn::Request for more information.
86
- def request_class
87
- Tynn::Request
362
+ # Returns <tt>true</tt> if the request method is <tt>POST</tt> and
363
+ # the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
364
+ #
365
+ # Tynn.define do
366
+ # on "users" do
367
+ # post do
368
+ # res.write("POST /users")
369
+ # end
370
+ # end
371
+ # end
372
+ #
373
+ def post
374
+ root? && req.post?
88
375
  end
89
376
 
90
- # Overrides response class used by Syro's router.
91
- # See Tynn::Response for more information.
92
- def response_class
93
- Tynn::Response
377
+ # Returns <tt>true</tt> if the request method is <tt>PATCH</tt> and
378
+ # the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
379
+ #
380
+ # Tynn.define do
381
+ # on "users" do
382
+ # on :id do |id|
383
+ # on patch do
384
+ # res.write("PATCH /users/#{ id }")
385
+ # end
386
+ # end
387
+ # end
388
+ # end
389
+ #
390
+ def patch
391
+ root? && req.patch?
392
+ end
393
+
394
+ # Returns <tt>true</tt> if the request method is <tt>PUT</tt> and
395
+ # the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
396
+ #
397
+ # Tynn.define do
398
+ # on "users" do
399
+ # on :id do |id|
400
+ # on put do
401
+ # res.write("PUT /users/#{ id }")
402
+ # end
403
+ # end
404
+ # end
405
+ # end
406
+ #
407
+ def put
408
+ root? && req.put?
409
+ end
410
+
411
+ # Returns <tt>true</tt> if the request method is <tt>DELETE</tt> and
412
+ # the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
413
+ #
414
+ # Tynn.define do
415
+ # on "users" do
416
+ # on :id do |id|
417
+ # on delete do
418
+ # res.write("DELETE /users/#{ id }")
419
+ # end
420
+ # end
421
+ # end
422
+ # end
423
+ #
424
+ def delete
425
+ root? && req.delete?
426
+ end
427
+
428
+ # Returns <tt>true</tt> if the path yet to be consumed is empty.
429
+ #
430
+ # Tynn.define do
431
+ # on root? do
432
+ # res.write("/")
433
+ # end
434
+ # end
435
+ #
436
+ def root?
437
+ @__seg.root?
94
438
  end
95
439
  end
96
440
  end