tynn 2.0.0.alpha → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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