nyny 1.0.0.pre1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +12 -0
- data/Gemfile +5 -0
- data/Performance.md +8 -6
- data/README.md +116 -55
- data/Rakefile +5 -0
- data/benchmarks/filters/{frankie.rb → nyny.rb} +2 -2
- data/benchmarks/helpers/{frankie.rb → nyny.rb} +2 -2
- data/benchmarks/simple/{frankie.rb → nyny.rb} +2 -2
- data/benchmarks/url_pattern/{frankie.rb → nyny.rb} +2 -2
- data/examples/active_record/server.rb +2 -2
- data/examples/data_mapper/Gemfile +7 -0
- data/examples/data_mapper/database.rb +7 -0
- data/examples/data_mapper/models/shout.rb +7 -0
- data/examples/data_mapper/server.rb +41 -0
- data/examples/json_api.rb +2 -2
- data/examples/templates/server.rb +5 -4
- data/examples/web_sockets/server.rb +4 -4
- data/lib/nyny.rb +4 -2
- data/lib/nyny/app.rb +46 -47
- data/lib/nyny/middleware_chain.rb +14 -0
- data/lib/nyny/request_scope.rb +3 -9
- data/lib/nyny/route_signature.rb +11 -11
- data/lib/nyny/router.rb +44 -0
- data/lib/nyny/runner.rb +18 -0
- data/lib/nyny/version.rb +1 -1
- data/nyny.gemspec +2 -2
- data/spec/app_spec.rb +34 -24
- data/spec/primitives_spec.rb +11 -4
- data/spec/request_scope_spec.rb +12 -2
- data/spec/route_signature_spec.rb +9 -0
- data/spec/runner_spec.rb +23 -0
- data/spec/spec_helper.rb +14 -3
- metadata +23 -15
- data/.ruby-gemset +0 -1
- data/lib/nyny/class_level_api.rb +0 -40
- data/spec/class_level_api_spec.rb +0 -39
@@ -0,0 +1,41 @@
|
|
1
|
+
#!ruby -I ../../lib -I lib
|
2
|
+
|
3
|
+
ENV['RACK_ENV'] ||= 'development'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'bundler'
|
7
|
+
Bundler.setup(:default, ENV['RACK_ENV'].to_sym)
|
8
|
+
|
9
|
+
require 'nyny'
|
10
|
+
require_relative 'database'
|
11
|
+
|
12
|
+
TEMPLATE = DATA.read.freeze
|
13
|
+
|
14
|
+
class App < NYNY::App
|
15
|
+
get '/' do
|
16
|
+
shouts = Shout.all.reverse
|
17
|
+
ERB.new(TEMPLATE).result(binding)
|
18
|
+
end
|
19
|
+
|
20
|
+
post '/shouts' do
|
21
|
+
Shout.create :body => params[:body]
|
22
|
+
redirect_to '/'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
App.run! 9000
|
27
|
+
|
28
|
+
__END__
|
29
|
+
<html>
|
30
|
+
<body>
|
31
|
+
<form action="/shouts" method="post">
|
32
|
+
<input type="text" name="body"></input>
|
33
|
+
<input type="submit" value="SHOUT"></input>
|
34
|
+
</form>
|
35
|
+
<ul>
|
36
|
+
<% shouts.each do |shout| %>
|
37
|
+
<li><%= shout.body %>
|
38
|
+
<% end %>
|
39
|
+
</ul>
|
40
|
+
</body>
|
41
|
+
</html>
|
data/examples/json_api.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
#!ruby -I ../lib -I lib
|
2
|
-
require '
|
2
|
+
require 'nyny'
|
3
3
|
require 'json'
|
4
4
|
|
5
5
|
#
|
6
6
|
# Every response of this app will be automatically converted to json
|
7
7
|
#
|
8
|
-
class App <
|
8
|
+
class App < NYNY::App
|
9
9
|
before { headers 'Content-Type' => 'application/json' }
|
10
10
|
|
11
11
|
after do
|
@@ -1,9 +1,10 @@
|
|
1
|
-
#!ruby -I
|
2
|
-
require '
|
1
|
+
#!ruby -I ../../lib -I lib
|
2
|
+
require 'nyny'
|
3
3
|
require 'sinatra'
|
4
4
|
require 'ostruct'
|
5
5
|
|
6
6
|
module Views
|
7
|
+
CACHE = Tilt::Cache.new
|
7
8
|
include ::Sinatra::Templates
|
8
9
|
|
9
10
|
def settings
|
@@ -11,12 +12,12 @@ module Views
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def template_cache
|
14
|
-
|
15
|
+
CACHE
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
19
|
|
19
|
-
class App <
|
20
|
+
class App < NYNY::App
|
20
21
|
helpers Views
|
21
22
|
|
22
23
|
get '/' do
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#!ruby -I ../../lib -I lib
|
2
|
-
require '
|
2
|
+
require 'nyny'
|
3
3
|
require 'faye/websocket'
|
4
4
|
|
5
5
|
#
|
@@ -37,7 +37,7 @@ class WebSockets
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
class App <
|
40
|
+
class App < NYNY::App
|
41
41
|
#Serve static assets from public folder
|
42
42
|
use Rack::Static, :urls => ["/public"]
|
43
43
|
|
@@ -52,8 +52,8 @@ class App < Frankie::App
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
get '/
|
56
|
-
'yep, you can still use
|
55
|
+
get '/nyny' do
|
56
|
+
'yep, you can still use nyny'
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
data/lib/nyny.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'uri'
|
2
2
|
require 'rack'
|
3
3
|
|
4
|
-
require
|
4
|
+
require 'nyny/version'
|
5
5
|
require 'nyny/primitives'
|
6
6
|
require 'nyny/request_scope'
|
7
7
|
require 'nyny/route_signature'
|
8
|
-
require 'nyny/
|
8
|
+
require 'nyny/runner'
|
9
|
+
require 'nyny/middleware_chain'
|
10
|
+
require 'nyny/router'
|
9
11
|
require 'nyny/app'
|
data/lib/nyny/app.rb
CHANGED
@@ -1,66 +1,65 @@
|
|
1
1
|
module NYNY
|
2
2
|
class App
|
3
|
-
|
4
|
-
|
5
|
-
RouteNotFoundError = Class.new StandardError
|
3
|
+
HTTP_VERBS = [:delete, :get, :head, :options, :patch, :post, :put, :trace]
|
4
|
+
extend Runner
|
6
5
|
|
6
|
+
attr_reader :middleware_chain, :router
|
7
7
|
def initialize app=nil
|
8
|
-
@
|
9
|
-
|
8
|
+
@router = Router.new({
|
9
|
+
:routes => self.class.routes,
|
10
|
+
:fallback => (app || lambda {|env| Response.new '', 404 }),
|
11
|
+
:before_hooks => self.class.before_hooks,
|
12
|
+
:after_hooks => self.class.after_hooks
|
13
|
+
})
|
14
|
+
@middleware_chain = MiddlewareChain.new(self.class.middlewares,
|
15
|
+
lambda {|env| _call(env)})
|
10
16
|
end
|
11
17
|
|
12
|
-
def
|
13
|
-
|
14
|
-
klass, args, blk = entry
|
15
|
-
klass.new prev, *args, &blk
|
16
|
-
end
|
18
|
+
def _call env
|
19
|
+
router.call env
|
17
20
|
end
|
18
21
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
begin
|
23
|
-
Rack::Handler::Thin
|
24
|
-
rescue LoadError
|
25
|
-
Rack::Handler::WEBrick
|
26
|
-
end.run new, :Port => port
|
22
|
+
def call env
|
23
|
+
middleware_chain.call env
|
27
24
|
end
|
28
25
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
#class methods
|
27
|
+
class << self
|
28
|
+
HTTP_VERBS.each do |method|
|
29
|
+
define_method method do |str, &blk|
|
30
|
+
(routes[method] ||= {})[RouteSignature.new(str)] = Proc.new &blk
|
31
|
+
end
|
33
32
|
end
|
34
33
|
|
35
|
-
|
36
|
-
|
34
|
+
def middlewares; @middlewares ||= [] end
|
35
|
+
def routes; @routes ||= {} end
|
36
|
+
def before_hooks; @before_hooks ||= [] end
|
37
|
+
def after_hooks; @after_hooks ||= [] end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
def use_protection! args={}
|
40
|
+
begin
|
41
|
+
require 'rack/protection'
|
42
|
+
middlewares.unshift [Rack::Protection, args]
|
43
|
+
rescue LoadError
|
44
|
+
puts "WARN: to use protection, you must install 'rack-protection' gem"
|
45
|
+
end
|
45
46
|
end
|
46
|
-
end
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
def before &blk
|
49
|
+
before_hooks << Proc.new(&blk)
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
_call env
|
55
|
-
else
|
56
|
-
if not @initialized_chain
|
57
|
-
@initialized_chain = true
|
58
|
-
@top.call(env)
|
59
|
-
else
|
60
|
-
@initialized_chain = false
|
61
|
-
_call env
|
62
|
-
end
|
52
|
+
def after &blk
|
53
|
+
after_hooks << Proc.new(&blk)
|
63
54
|
end
|
64
|
-
|
55
|
+
|
56
|
+
def use middleware, *args, &block
|
57
|
+
middlewares << [middleware, args, block]
|
58
|
+
end
|
59
|
+
|
60
|
+
def helpers *args
|
61
|
+
args.each {|m| RequestScope.add_helper_module m }
|
62
|
+
end
|
63
|
+
end #class methods
|
65
64
|
end
|
66
65
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module NYNY
|
2
|
+
class MiddlewareChain
|
3
|
+
def initialize middlewares, proxy
|
4
|
+
@top = middlewares.reverse.reduce (proxy) do |prev, entry|
|
5
|
+
klass, args, blk = entry
|
6
|
+
klass.new prev, *args, &blk
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def call env
|
11
|
+
@top.call(env)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/nyny/request_scope.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
module NYNY
|
2
2
|
class RequestScope
|
3
|
-
attr_reader :request, :
|
3
|
+
attr_reader :request, :response
|
4
4
|
|
5
5
|
def self.add_helper_module m
|
6
6
|
include m
|
7
7
|
end
|
8
8
|
|
9
|
-
def initialize
|
10
|
-
@app = app
|
9
|
+
def initialize request
|
11
10
|
@headers = {'Content-Type' => 'text/html'}
|
12
11
|
@status = 200
|
13
|
-
@request =
|
12
|
+
@request = request
|
14
13
|
end
|
15
14
|
|
16
15
|
def params
|
@@ -42,17 +41,12 @@ module NYNY
|
|
42
41
|
end
|
43
42
|
|
44
43
|
def apply_to &handler
|
45
|
-
params.default_proc = proc {|h,k| h[k.to_s] || h[k.to_sym]}
|
46
|
-
app.class.before_hooks.each {|h| instance_eval &h }
|
47
|
-
|
48
44
|
@response = @halt_response || begin
|
49
45
|
Response.new instance_eval(&handler), @status, @headers
|
50
46
|
end
|
51
47
|
|
52
48
|
cookies.each {|k,v| @response.set_cookie k,v }
|
53
49
|
@response.redirect(@redirect) if @redirect
|
54
|
-
|
55
|
-
app.class.after_hooks.each {|h| instance_eval &h }
|
56
50
|
@response.finish
|
57
51
|
@response
|
58
52
|
end
|
data/lib/nyny/route_signature.rb
CHANGED
@@ -3,27 +3,27 @@ module NYNY
|
|
3
3
|
NAME_PATTERN = /:(\S+)/
|
4
4
|
|
5
5
|
attr_reader :pattern
|
6
|
-
def initialize
|
7
|
-
@pattern =
|
8
|
-
route
|
9
|
-
else
|
10
|
-
pattern_for route.dup
|
11
|
-
end
|
6
|
+
def initialize signature
|
7
|
+
@pattern = pattern_for signature
|
12
8
|
end
|
13
9
|
|
14
10
|
def pattern_for string
|
11
|
+
return string if string.is_a? Regexp
|
15
12
|
return string unless string.include? ':'
|
16
|
-
string = "/#{string}" unless string.start_with? '/'
|
17
|
-
parts = string.split '/'
|
18
13
|
|
19
|
-
|
14
|
+
signature = string.start_with?('/') ? string : "/#{string}"
|
15
|
+
build_regex signature
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_regex signature
|
19
|
+
groups = signature.split('/').map do |part|
|
20
20
|
next part if part.empty?
|
21
21
|
next part unless part.start_with? ':'
|
22
22
|
name = NAME_PATTERN.match(part)[1]
|
23
23
|
%Q{(?<#{name}>\\S+)}
|
24
|
-
end.select {|s| !s.empty? }
|
24
|
+
end.select {|s| !s.empty? }.join('\/')
|
25
25
|
|
26
|
-
%r(\/#{groups
|
26
|
+
%r(\/#{groups})
|
27
27
|
end
|
28
28
|
|
29
29
|
def match path
|
data/lib/nyny/router.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module NYNY
|
2
|
+
class Router
|
3
|
+
NullHandler = Class.new
|
4
|
+
|
5
|
+
attr_reader :fallback, :routes, :before_hooks, :after_hooks
|
6
|
+
def initialize options
|
7
|
+
@fallback = options[:fallback]
|
8
|
+
@routes = options[:routes]
|
9
|
+
@before_hooks = options[:before_hooks]
|
10
|
+
@after_hooks = options[:after_hooks]
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_handler request
|
14
|
+
routes.fetch(request.request_method.downcase.to_sym, []).each do |sig, h|
|
15
|
+
params = sig.match request.path
|
16
|
+
return [h, params] if params
|
17
|
+
end
|
18
|
+
|
19
|
+
[NullHandler, {}]
|
20
|
+
end
|
21
|
+
|
22
|
+
def call env
|
23
|
+
req = Request.new(env)
|
24
|
+
handler, params = find_handler req
|
25
|
+
|
26
|
+
if handler != NullHandler
|
27
|
+
process req, handler, params
|
28
|
+
else
|
29
|
+
fallback.call env
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def process request, handler, url_params
|
34
|
+
request.params.merge! url_params
|
35
|
+
request.params.default_proc = proc {|h,k| h[k.to_s] || h[k.to_sym]}
|
36
|
+
|
37
|
+
scope = RequestScope.new(request)
|
38
|
+
before_hooks.each {|h| scope.instance_eval &h }
|
39
|
+
response = scope.apply_to &handler
|
40
|
+
after_hooks.each {|h| scope.instance_eval &h }
|
41
|
+
response
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/nyny/runner.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module NYNY
|
2
|
+
module Runner
|
3
|
+
def optimal_runner
|
4
|
+
return Rack::Handler::WEBrick if RUBY_PLATFORM == 'java'
|
5
|
+
|
6
|
+
begin
|
7
|
+
Rack::Handler::Thin
|
8
|
+
rescue LoadError
|
9
|
+
Rack::Handler::WEBrick
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def run! port=9292
|
14
|
+
middlewares.unshift Rack::ShowExceptions, Rack::CommonLogger
|
15
|
+
optimal_runner.run new, :Port => port
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/nyny/version.rb
CHANGED
data/nyny.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = NYNY::VERSION
|
9
9
|
spec.authors = ["Andrei Lisnic"]
|
10
10
|
spec.email = ["andrei.lisnic@gmail.com"]
|
11
|
-
spec.description = %q{New York, New York.}
|
12
|
-
spec.summary = %q{
|
11
|
+
spec.description = %q{New York, New York - (very) small Sinatra clone.}
|
12
|
+
spec.summary = %q{New York, New York.}
|
13
13
|
spec.homepage = "https://github.com/alisnic/nyny"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
data/spec/app_spec.rb
CHANGED
@@ -1,16 +1,8 @@
|
|
1
|
-
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe App do
|
4
4
|
let (:app) { mock_app {} }
|
5
5
|
|
6
|
-
describe '.run!' do
|
7
|
-
#
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'should have the class methods included' do
|
11
|
-
extended_modules_for(App).should include(ClassLevelApi)
|
12
|
-
end
|
13
|
-
|
14
6
|
it 'should return a rack response on call' do
|
15
7
|
response = app.get '/'
|
16
8
|
response.should be_a(Rack::Response)
|
@@ -21,9 +13,17 @@ describe App do
|
|
21
13
|
response.status.should == 404
|
22
14
|
end
|
23
15
|
|
16
|
+
it '.use_protection! should add protection middleware on top' do
|
17
|
+
app_class = mock_app_class do
|
18
|
+
use_protection!
|
19
|
+
end
|
20
|
+
|
21
|
+
app_class.middlewares.first.should == [Rack::Protection, {}]
|
22
|
+
end
|
23
|
+
|
24
24
|
it 'should match a route for any supported verbs' do
|
25
25
|
url = random_url
|
26
|
-
verb =
|
26
|
+
verb = App::HTTP_VERBS.sample
|
27
27
|
|
28
28
|
app = mock_app do
|
29
29
|
send verb, url do
|
@@ -82,7 +82,7 @@ describe App do
|
|
82
82
|
[210, {}, ['Hello from downstream']]
|
83
83
|
end
|
84
84
|
|
85
|
-
app_class =
|
85
|
+
app_class = mock_app_class do
|
86
86
|
get '/' do
|
87
87
|
'hello'
|
88
88
|
end
|
@@ -122,23 +122,33 @@ describe App do
|
|
122
122
|
res.headers['Set-Cookie'].should == 'foo=bar'
|
123
123
|
end
|
124
124
|
|
125
|
-
describe '
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
125
|
+
describe 'Class level api' do
|
126
|
+
let (:app_class) { Class.new(App) }
|
127
|
+
describe 'middlewares' do
|
128
|
+
let (:app_class) do
|
129
|
+
mock_app_class do
|
130
|
+
use NullMiddleware
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should allow to add a middleware' do
|
135
|
+
app_class.middlewares.last.first.should == NullMiddleware
|
136
|
+
end
|
133
137
|
end
|
134
138
|
|
135
|
-
|
136
|
-
|
139
|
+
describe 'helpers' do
|
140
|
+
it 'should allow to include a helper in request scope' do
|
141
|
+
app_class.helpers NullHelper
|
142
|
+
RequestScope.ancestors.should include(NullHelper)
|
137
143
|
end
|
138
144
|
|
139
|
-
|
140
|
-
|
141
|
-
|
145
|
+
it 'should allow to include multiple helpers modules' do
|
146
|
+
module NullHelper2
|
147
|
+
end
|
148
|
+
|
149
|
+
app_class.helpers NullHelper, NullHelper2
|
150
|
+
RequestScope.ancestors.should include(NullHelper, NullHelper2)
|
151
|
+
end
|
142
152
|
end
|
143
153
|
end
|
144
154
|
end
|